diff --git a/src/components/AddEnvie/index.tsx b/src/components/AddEnvie/index.tsx index 27251ad..8c68a92 100644 --- a/src/components/AddEnvie/index.tsx +++ b/src/components/AddEnvie/index.tsx @@ -3,7 +3,7 @@ import { toast } from "react-toastify" import { AppDispatch } from "../../store" -import { sendAddEnvie } from "../../store/envieAdd" +import { fetchEnvieAdd } from "../../store/envieAdd" import styles from "./styles.module.scss" interface Props { @@ -29,7 +29,7 @@ const AddEnvie = ({ dispatch }: Props) => { const onSavePostClicked = () => { if (domaine && envies) { dispatch( - sendAddEnvie({ + fetchEnvieAdd({ domaine, envies, precisions, diff --git a/src/components/MembreSet/index.tsx b/src/components/MembreSet/index.tsx index 3cb6aa2..9fafc9b 100644 --- a/src/components/MembreSet/index.tsx +++ b/src/components/MembreSet/index.tsx @@ -3,7 +3,7 @@ import { toast } from "react-toastify" import { AppDispatch } from "../../store" -import { sendMembreSet } from "../../store/membreSet" +import { fetchMembreSet } from "../../store/membreSet" import { Membre } from "../../services/membres" import styles from "./styles.module.scss" @@ -24,7 +24,7 @@ const MembreSet = ({ dispatch, membre }: Props) => { const onSavePostClicked = () => { if (prenom && nom) { dispatch( - sendMembreSet({ + fetchMembreSet({ ...membre, prenom, nom, diff --git a/src/gsheets/utils.ts b/src/gsheets/accessors.ts similarity index 97% rename from src/gsheets/utils.ts rename to src/gsheets/accessors.ts index 2dbb23d..c09534d 100644 --- a/src/gsheets/utils.ts +++ b/src/gsheets/accessors.ts @@ -6,10 +6,9 @@ import { GoogleSpreadsheet, GoogleSpreadsheetWorksheet } from "google-spreadshee const SCOPES = ["https://www.googleapis.com/auth/spreadsheets"] const CRED_PATH = path.resolve(process.cwd(), "access/gsheets.json") -type ElementWithId = unknown & { id: number } +export type ElementWithId = unknown & { id: number } -// eslint-disable-next-line @typescript-eslint/ban-types -export async function getList( +export async function listGet( sheetName: string, specimen: Element ): Promise { @@ -38,17 +37,15 @@ export async function getList( return elements } -// eslint-disable-next-line @typescript-eslint/ban-types export async function get( sheetName: string, membreId: number, specimen: Element ): Promise { - const list = await getList(sheetName, specimen) + const list = await listGet(sheetName, specimen) return list.find((element) => element.id === membreId) } -// eslint-disable-next-line @typescript-eslint/ban-types export async function setList( sheetName: string, elements: Element[] @@ -101,7 +98,6 @@ export async function setList( return true } -// eslint-disable-next-line @typescript-eslint/ban-types export async function set( sheetName: string, element: Element diff --git a/src/gsheets/envies.ts b/src/gsheets/envies.ts index 28bf86c..e8f9d7f 100644 --- a/src/gsheets/envies.ts +++ b/src/gsheets/envies.ts @@ -1,33 +1,12 @@ -import { Request, Response, NextFunction } from "express" -import { getList, add } from "./utils" +import { listGetRequest, getRequest, setRequest, addRequest } from "./expressAccessors" import { Envie, EnvieWithoutId } from "../services/envies" -export const envieListGet = async ( - _request: Request, - response: Response, - _next: NextFunction -): Promise => { - try { - const list = await getList("Envies d'aider", new Envie()) - if (list) { - response.status(200).json(list) - } - } catch (e: unknown) { - response.status(400).json(e) - } -} +const sheetName = "Envies d'aider" -export const envieAdd = async ( - request: Request, - response: Response, - _next: NextFunction -): Promise => { - try { - const envie = await add("Envies d'aider", request.body) - if (envie) { - response.status(200).json(envie) - } - } catch (e: unknown) { - response.status(400).json(e) - } -} +export const envieListGet = listGetRequest(sheetName, new Envie()) + +export const envieGet = getRequest(sheetName, new Envie()) + +export const envieAdd = addRequest(sheetName) + +export const envieSet = setRequest(sheetName) diff --git a/src/gsheets/expressAccessors.ts b/src/gsheets/expressAccessors.ts new file mode 100644 index 0000000..b05e7b3 --- /dev/null +++ b/src/gsheets/expressAccessors.ts @@ -0,0 +1,61 @@ +import { Request, Response, NextFunction } from "express" +import { ElementWithId, get, listGet, add, set } from "./accessors" + +export function getRequest(sheetName: string, specimen: Element) { + return async (request: Request, response: Response, _next: NextFunction): Promise => { + try { + const id = parseInt(request.query.id as string, 10) || -1 + const elements = await get(sheetName, id, specimen) + if (elements) { + response.status(200).json(elements) + } + } catch (e: unknown) { + response.status(400).json(e) + } + } +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function addRequest( + sheetName: string +) { + return async (request: Request, response: Response, _next: NextFunction): Promise => { + try { + const element = await add(sheetName, request.body) + if (element) { + response.status(200).json(element) + } + } catch (e: unknown) { + response.status(400).json(e) + } + } +} + +export function listGetRequest( + sheetName: string, + specimen: Element +) { + return async (_request: Request, response: Response, _next: NextFunction): Promise => { + try { + const elements = await listGet(sheetName, specimen) + if (elements) { + response.status(200).json(elements) + } + } catch (e: unknown) { + response.status(400).json(e) + } + } +} + +export function setRequest(sheetName: string) { + return async (request: Request, response: Response, _next: NextFunction): Promise => { + try { + const element = await set(sheetName, request.body) + if (element) { + response.status(200).json(element) + } + } catch (e: unknown) { + response.status(400).json(e) + } + } +} diff --git a/src/gsheets/jeuJav.ts b/src/gsheets/jeuJav.ts index 7c7e9cd..4a0ad96 100644 --- a/src/gsheets/jeuJav.ts +++ b/src/gsheets/jeuJav.ts @@ -1,31 +1,12 @@ -import { Request, Response, NextFunction } from "express" -import _ from "lodash" -import { getList } from "./utils" -import { JeuJav } from "../services/jeuJav" +import { listGetRequest, getRequest, setRequest, addRequest } from "./expressAccessors" +import { JeuJav, JeuJavWithoutId } from "../services/jeuxJav" -export const jeuJavListGet = async ( - _request: Request, - response: Response, - _next: NextFunction -): Promise => { - try { - const list = await getList("Jeux JAV", new JeuJav()) - if (list) { - response.status(200).json(list) - } - } catch (e: unknown) { - response.status(400).json(e) - } -} +const sheetName = "Jeux JAV" -export const jeuJavGet = async ( - _request: Request, - response: Response, - _next: NextFunction -): Promise => { - const list = await getList("Jeux JAV", new JeuJav()) - const data = _.find(list, { id: 56 }) - if (data) { - response.status(200).json(data) - } -} +export const jeuJavListGet = listGetRequest(sheetName, new JeuJav()) + +export const jeuJavGet = getRequest(sheetName, new JeuJav()) + +export const jeuJavAdd = addRequest(sheetName) + +export const jeuJavSet = setRequest(sheetName) diff --git a/src/gsheets/membres.ts b/src/gsheets/membres.ts index 2e693fe..35f34f1 100644 --- a/src/gsheets/membres.ts +++ b/src/gsheets/membres.ts @@ -1,62 +1,12 @@ -import { Request, Response, NextFunction } from "express" -import { getList, get, set, add } from "./utils" +import { listGetRequest, getRequest, setRequest, addRequest } from "./expressAccessors" import { Membre, MembreWithoutId } from "../services/membres" -export const membreListGet = async ( - _request: Request, - response: Response, - _next: NextFunction -): Promise => { - try { - const list = await getList("Membres", new Membre()) - if (list) { - response.status(200).json(list) - } - } catch (e: unknown) { - response.status(400).json(e) - } -} +const sheetName = "Membres" -export const membreGet = async ( - request: Request, - response: Response, - _next: NextFunction -): Promise => { - try { - const id = parseInt(request.query.id as string, 10) || -1 - const membre = await get("Membres", id, new Membre()) - response.status(200).json(membre) - } catch (e: unknown) { - response.status(400).json(e) - } -} +export const membreListGet = listGetRequest(sheetName, new Membre()) -export const membreSet = async ( - request: Request, - response: Response, - _next: NextFunction -): Promise => { - try { - const envie = await set("Membres", request.body) - if (envie) { - response.status(200).json(envie) - } - } catch (e: unknown) { - response.status(400).json(e) - } -} +export const membreGet = getRequest(sheetName, new Membre()) -export const membreAdd = async ( - request: Request, - response: Response, - _next: NextFunction -): Promise => { - try { - const membre = await add("Membres", request.body) - if (membre) { - response.status(200).json(membre) - } - } catch (e: unknown) { - response.status(400).json(e) - } -} +export const membreAdd = addRequest(sheetName) + +export const membreSet = setRequest(sheetName) diff --git a/src/pages/MembrePage/MembrePage.tsx b/src/pages/MembrePage/MembrePage.tsx index 03ee586..bbd1642 100755 --- a/src/pages/MembrePage/MembrePage.tsx +++ b/src/pages/MembrePage/MembrePage.tsx @@ -4,7 +4,7 @@ import { useDispatch, useSelector, shallowEqual } from "react-redux" import { Helmet } from "react-helmet" import { AppState, AppThunk } from "../../store" -import { fetchMembreDataIfNeed } from "../../store/membre" +import { fetchMembreIfNeed } from "../../store/membre" import { MembreInfo, MembreSet } from "../../components" import styles from "./styles.module.scss" @@ -17,7 +17,7 @@ const MembrePage = ({ match }: Props): JSX.Element => { const membre = useSelector((state: AppState) => state.membre, shallowEqual) useEffect(() => { - dispatch(fetchMembreDataIfNeed(id)) + dispatch(fetchMembreIfNeed(id)) }, [dispatch, id]) const renderInfo = () => { @@ -48,6 +48,6 @@ interface LoadDataArgs { params: { id: number } } -export const loadData = ({ params }: LoadDataArgs): AppThunk[] => [fetchMembreDataIfNeed(params.id)] +export const loadData = ({ params }: LoadDataArgs): AppThunk[] => [fetchMembreIfNeed(params.id)] export default memo(MembrePage) diff --git a/src/pages/MembrePage/__tests__/MembrePage.tsx b/src/pages/MembrePage/__tests__/MembrePage.tsx index d0d770e..6853edd 100755 --- a/src/pages/MembrePage/__tests__/MembrePage.tsx +++ b/src/pages/MembrePage/__tests__/MembrePage.tsx @@ -4,7 +4,7 @@ import { render } from "@testing-library/react" import { MemoryRouter } from "react-router-dom" -import { fetchMembreDataIfNeed } from "../../../store/membre" +import { fetchMembreIfNeed } from "../../../store/membre" import mockStore from "../../../utils/mockStore" import MembrePage from "../MembrePage" @@ -45,7 +45,7 @@ describe("", () => { const { dispatch } = renderHelper() expect(dispatch).toHaveBeenCalledTimes(1) - expect(dispatch.mock.calls[0][0].toString()).toBe(fetchMembreDataIfNeed(id).toString()) + expect(dispatch.mock.calls[0][0].toString()).toBe(fetchMembreIfNeed(id).toString()) }) it("renders the loading status if data invalid", () => { diff --git a/src/services/accessors.ts b/src/services/accessors.ts new file mode 100644 index 0000000..5369e56 --- /dev/null +++ b/src/services/accessors.ts @@ -0,0 +1,85 @@ +import axios from "axios" + +import config from "../config" + +export type ElementWithId = unknown & { id: number } + +export function get(elementName: string): (id: number) => Promise<{ + data?: Element + error?: Error +}> { + interface ElementGetResponse { + data?: Element + error?: Error + } + return async (id: number): Promise => { + try { + const { data } = await axios.get(`${config.API_URL}/${elementName}Get`, { + params: { id }, + }) + return { data } + } catch (error) { + return { error: error as Error } + } + } +} + +export function listGet(elementName: string): () => Promise<{ + data?: Element[] + error?: Error +}> { + interface ElementListGetResponse { + data?: Element[] + error?: Error + } + return async (): Promise => { + try { + const { data } = await axios.get(`${config.API_URL}/${elementName}ListGet`) + return { data } + } catch (error) { + return { error: error as Error } + } + } +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function add( + elementName: string +): (membreWithoutId: ElementNoId) => Promise<{ + data?: Element + error?: Error +}> { + interface ElementGetResponse { + data?: Element + error?: Error + } + return async (membreWithoutId: ElementNoId): Promise => { + try { + const { data } = await axios.post( + `${config.API_URL}/${elementName}Add`, + membreWithoutId + ) + return { data } + } catch (error) { + return { error: error as Error } + } + } +} + +export function set(elementName: string): (membre: Element) => Promise<{ + data?: Element + error?: Error +}> { + interface ElementGetResponse { + data?: Element + error?: Error + } + return async (membre: Element): Promise => { + try { + const { data } = await axios.post(`${config.API_URL}/${elementName}Set`, membre) + return { data } + } catch (error) { + return { error: error as Error } + } + } +} diff --git a/src/services/envies.ts b/src/services/envies.ts index e04d06b..69a9579 100644 --- a/src/services/envies.ts +++ b/src/services/envies.ts @@ -1,6 +1,4 @@ -import axios from "axios" - -import config from "../config" +import { get, listGet, add, set } from "./accessors" export class Envie { id = 0 @@ -15,30 +13,13 @@ export class Envie { dateAjout = "" } + export type EnvieWithoutId = Omit -export interface EnvieListGetResponse { - data?: Envie[] - error?: Error -} -export const envieListGet = async (): Promise => { - try { - const { data } = await axios.get(`${config.API_URL}/EnvieListGet`) - return { data } - } catch (error) { - return { error: error as Error } - } -} +export const envieGet = get("Envie") -export interface EnvieAddResponse { - data?: Envie - error?: Error -} -export const envieAdd = async (envieWithoutId: EnvieWithoutId): Promise => { - try { - const { data } = await axios.post(`${config.API_URL}/EnvieAdd`, envieWithoutId) - return { data } - } catch (error) { - return { error: error as Error } - } -} +export const envieListGet = listGet("Envie") + +export const envieAdd = add("Envie") + +export const envieSet = set("Envie") diff --git a/src/services/jeuJav.ts b/src/services/jeuJav.ts deleted file mode 100644 index 038bade..0000000 --- a/src/services/jeuJav.ts +++ /dev/null @@ -1,61 +0,0 @@ -import axios from "axios" - -import config from "../config" - -export class JeuJav { - id = 0 - - titre = "" - - auteur = "" - - editeur = "" - - minJoueurs = 0 - - maxJoueurs = 0 - - duree = 0 - - type: "Ambiance" | "Famille" | "Expert" | "" = "" - - poufpaf = "" - - bggId = 0 - - exemplaires = 1 - - dispoPret = 0 - - nonRangee = 0 - - ean = "" - - bggPhoto = "" -} - -export interface JeuJavListResponse { - data?: JeuJav[] - error?: Error -} -export const getJeuJavList = async (): Promise => { - try { - const { data } = await axios.get(`${config.API_URL}/JeuJavListGet`) - return { data } - } catch (error) { - return { error: error as Error } - } -} - -export interface JeuJavResponse { - data?: JeuJav - error?: Error -} -export const getJeuJavData = async (id: string): Promise => { - try { - const { data } = await axios.get(`${config.API_URL}/JeuJavGet`, { params: { id } }) - return { data } - } catch (error) { - return { error: error as Error } - } -} diff --git a/src/services/jeuxJav.ts b/src/services/jeuxJav.ts new file mode 100644 index 0000000..0c9e118 --- /dev/null +++ b/src/services/jeuxJav.ts @@ -0,0 +1,43 @@ +import { get, listGet, add, set } from "./accessors" + +export class JeuJav { + id = 0 + + titre = "" + + auteur = "" + + editeur = "" + + minJoueurs = 0 + + maxJoueurs = 0 + + duree = 0 + + type: "Ambiance" | "Famille" | "Expert" | "" = "" + + poufpaf = "" + + bggId = 0 + + exemplaires = 1 + + dispoPret = 0 + + nonRangee = 0 + + ean = "" + + bggPhoto = "" +} + +export type JeuJavWithoutId = Omit + +export const jeuJavGet = get("JeuJav") + +export const jeuJavListGet = listGet("JeuJav") + +export const jeuJavAdd = add("JeuJav") + +export const jeuJavSet = set("JeuJav") diff --git a/src/services/membres.ts b/src/services/membres.ts index de0e238..49e2534 100644 --- a/src/services/membres.ts +++ b/src/services/membres.ts @@ -1,6 +1,4 @@ -import axios from "axios" - -import config from "../config" +import { get, listGet, add, set } from "./accessors" export class Membre { id = 0 @@ -29,56 +27,13 @@ export class Membre { passe = "" } + export type MembreWithoutId = Omit -export interface MembreListGetResponse { - data?: Membre[] - error?: Error -} -export const membreListGet = async (): Promise => { - try { - const { data } = await axios.get(`${config.API_URL}/MembreListGet`) - return { data } - } catch (error) { - return { error: error as Error } - } -} +export const membreGet = get("Membre") -export interface MembreGetResponse { - data?: Membre - error?: Error -} -export const membreGet = async (id: number): Promise => { - try { - const { data } = await axios.get(`${config.API_URL}/MembreGet`, { params: { id } }) - return { data } - } catch (error) { - return { error: error as Error } - } -} +export const membreListGet = listGet("Membre") -export interface MembreSetResponse { - data?: Membre - error?: Error -} -export const membreSet = async (membre: Membre): Promise => { - try { - const { data } = await axios.post(`${config.API_URL}/MembreSet`, membre) - return { data } - } catch (error) { - return { error: error as Error } - } -} +export const membreAdd = add("Membre") -export interface MembreAddResponse { - data?: Membre - error?: Error -} -export const membreAdd = async (membreWithoutId: MembreWithoutId): Promise => { - try { - const { data } = await axios.post(`${config.API_URL}/MembreAdd`, membreWithoutId) - return { data } - } catch (error) { - return { error: error as Error } - } -} +export const membreSet = set("Membre") diff --git a/src/store/__tests__/membre.ts b/src/store/__tests__/membre.ts index 3eb1bec..a6600e1 100644 --- a/src/store/__tests__/membre.ts +++ b/src/store/__tests__/membre.ts @@ -1,13 +1,7 @@ import axios from "axios" import mockStore from "../../utils/mockStore" -import membre, { - getRequesting, - getSuccess, - getFailure, - fetchMembreData, - initialState, -} from "../membre" +import membre, { getRequesting, getSuccess, getFailure, fetchMembre, initialState } from "../membre" jest.mock("axios") @@ -71,7 +65,7 @@ describe("membre action", () => { // @ts-expect-error axios.get.mockResolvedValue({ data: mockData }) - await dispatch(fetchMembreData(id)) + await dispatch(fetchMembre(id)) expect(getActions()).toEqual(expectedActions) }) @@ -85,7 +79,7 @@ describe("membre action", () => { // @ts-expect-error axios.get.mockRejectedValue({ message: mockError }) - await dispatch(fetchMembreData(id)) + await dispatch(fetchMembre(id)) expect(getActions()).toEqual(expectedActions) }) }) diff --git a/src/store/envieAdd.ts b/src/store/envieAdd.ts index 28f6591..75dd0f2 100644 --- a/src/store/envieAdd.ts +++ b/src/store/envieAdd.ts @@ -1,13 +1,9 @@ import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit" -import { toast } from "react-toastify" -import { StateRequest } from "./utils" -import { Envie, EnvieWithoutId, envieAdd } from "../services/envies" -import { AppThunk } from "." +import { StateRequest, toastError, toastSuccess, elementAddFetch } from "./utils" +import { Envie, envieAdd } from "../services/envies" -const envieAdapter = createEntityAdapter({ - selectId: (envie) => envie.id, -}) +const envieAdapter = createEntityAdapter() const envieAddSlice = createSlice({ name: "addEnvie", @@ -32,34 +28,11 @@ const envieAddSlice = createSlice({ export default envieAddSlice.reducer export const { getRequesting, getSuccess, getFailure } = envieAddSlice.actions -export const sendAddEnvie = - (envieWithoutId: EnvieWithoutId): AppThunk => - async (dispatch) => { - dispatch(getRequesting()) - - const { error, data } = await envieAdd(envieWithoutId) - - if (error) { - dispatch(getFailure(error.message)) - toast.error(`Erreur lors de l'ajout d'une envie: ${error.message}`, { - position: "top-center", - autoClose: 6000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - progress: undefined, - }) - } else { - dispatch(getSuccess(data as Envie)) - toast.success("Envie ajoutée !", { - position: "top-center", - autoClose: 3000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - progress: undefined, - }) - } - } +export const fetchEnvieAdd = elementAddFetch( + envieAdd, + getRequesting, + getSuccess, + getFailure, + (error: Error) => toastError(`Erreur lors de l'ajout d'une envie: ${error.message}`), + () => toastSuccess("Envie ajoutée !") +) diff --git a/src/store/envieList.ts b/src/store/envieList.ts index 9f14eae..0b93878 100644 --- a/src/store/envieList.ts +++ b/src/store/envieList.ts @@ -1,13 +1,10 @@ import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit" -import { toast } from "react-toastify" -import { StateRequest } from "./utils" +import { StateRequest, toastError, elementListFetch } from "./utils" import { Envie, envieListGet } from "../services/envies" import { AppThunk, AppState } from "." -const envieAdapter = createEntityAdapter({ - selectId: (envie) => envie.id, -}) +const envieAdapter = createEntityAdapter() const envieList = createSlice({ name: "getEnvieList", @@ -32,26 +29,13 @@ const envieList = createSlice({ export default envieList.reducer export const { getRequesting, getSuccess, getFailure } = envieList.actions -export const fetchEnvieList = (): AppThunk => async (dispatch) => { - dispatch(getRequesting()) - - const { error, data } = await envieListGet() - - if (error) { - dispatch(getFailure(error.message)) - toast.error(`Erreur lors du chargement des envies: ${error.message}`, { - position: "top-center", - autoClose: 6000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - progress: undefined, - }) - } else { - dispatch(getSuccess(data as Envie[])) - } -} +export const fetchEnvieList = elementListFetch( + envieListGet, + getRequesting, + getSuccess, + getFailure, + (error: Error) => toastError(`Erreur lors du chargement des envies: ${error.message}`) +) const shouldFetchEnvieList = (state: AppState) => state.envieList.readyStatus !== "success" diff --git a/src/store/jeuJavList.ts b/src/store/jeuJavList.ts index 2645fa3..44f3fc1 100644 --- a/src/store/jeuJavList.ts +++ b/src/store/jeuJavList.ts @@ -1,13 +1,10 @@ import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit" -import { toast } from "react-toastify" -import { StateRequest } from "./utils" -import { JeuJav, getJeuJavList } from "../services/jeuJav" +import { StateRequest, toastError, elementListFetch } from "./utils" +import { JeuJav, jeuJavListGet } from "../services/jeuxJav" import { AppThunk, AppState } from "." -const jeuJavAdapter = createEntityAdapter({ - selectId: (jeuJav) => jeuJav.id, -}) +const jeuJavAdapter = createEntityAdapter() export const initialState = jeuJavAdapter.getInitialState({ readyStatus: "idle", @@ -34,26 +31,13 @@ const jeuJavList = createSlice({ export default jeuJavList.reducer export const { getRequesting, getSuccess, getFailure } = jeuJavList.actions -export const fetchJeuJavList = (): AppThunk => async (dispatch) => { - dispatch(getRequesting()) - - const { error, data } = await getJeuJavList() - - if (error) { - dispatch(getFailure(error.message)) - toast.error(`Erreur lors du chargement des jeux JAV: ${error.message}`, { - position: "top-center", - autoClose: 6000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - progress: undefined, - }) - } else { - dispatch(getSuccess(data as JeuJav[])) - } -} +export const fetchJeuJavList = elementListFetch( + jeuJavListGet, + getRequesting, + getSuccess, + getFailure, + (error: Error) => toastError(`Erreur lors du chargement des jeux JAV: ${error.message}`) +) const shouldFetchJeuJavList = (state: AppState) => state.jeuJavList.readyStatus !== "success" diff --git a/src/store/membre.ts b/src/store/membre.ts index 2c569ca..eee6c2a 100644 --- a/src/store/membre.ts +++ b/src/store/membre.ts @@ -1,7 +1,6 @@ import { PayloadAction, createSlice } from "@reduxjs/toolkit" -import { toast } from "react-toastify" -import { StateRequest } from "./utils" +import { StateRequest, toastError, elementFetch } from "./utils" import { Membre, membreGet } from "../services/membres" import { AppThunk, AppState } from "." @@ -32,36 +31,21 @@ const membre = createSlice({ export default membre.reducer export const { getRequesting, getSuccess, getFailure } = membre.actions -export const fetchMembreData = - (id: number): AppThunk => - async (dispatch) => { - dispatch(getRequesting()) +export const fetchMembre = elementFetch( + membreGet, + getRequesting, + getSuccess, + getFailure, + (error: Error) => toastError(`Erreur lors du chargement d'un membre: ${error.message}`) +) - const { error, data } = await membreGet(id) - - if (error) { - dispatch(getFailure(error.message)) - toast.error(`Erreur lors du chargement du membre ${id}: ${error.message}`, { - position: "top-center", - autoClose: 6000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - progress: undefined, - }) - } else { - dispatch(getSuccess(data as Membre)) - } - } - -const shouldFetchMembreData = (state: AppState, id: number) => +const shouldFetchMembre = (state: AppState, id: number) => state.membre.readyStatus !== "success" || (state.membre.entity && state.membre.entity.id !== id) -export const fetchMembreDataIfNeed = +export const fetchMembreIfNeed = (id: number): AppThunk => (dispatch, getState) => { - if (shouldFetchMembreData(getState(), id)) return dispatch(fetchMembreData(id)) + if (shouldFetchMembre(getState(), id)) return dispatch(fetchMembre(id)) return null } diff --git a/src/store/membreAdd.ts b/src/store/membreAdd.ts new file mode 100644 index 0000000..667f76a --- /dev/null +++ b/src/store/membreAdd.ts @@ -0,0 +1,38 @@ +import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit" + +import { StateRequest, toastError, toastSuccess, elementAddFetch } from "./utils" +import { Membre, membreAdd } from "../services/membres" + +const membreAdapter = createEntityAdapter() + +const membreAddSlice = createSlice({ + name: "addMembre", + initialState: membreAdapter.getInitialState({ + readyStatus: "idle", + } as StateRequest), + reducers: { + getRequesting: (state) => { + state.readyStatus = "request" + }, + getSuccess: (state, { payload }: PayloadAction) => { + state.readyStatus = "success" + membreAdapter.addOne(state, payload) + }, + getFailure: (state, { payload }: PayloadAction) => { + state.readyStatus = "failure" + state.error = payload + }, + }, +}) + +export default membreAddSlice.reducer +export const { getRequesting, getSuccess, getFailure } = membreAddSlice.actions + +export const fetchMembreAdd = elementAddFetch( + membreAdd, + getRequesting, + getSuccess, + getFailure, + (error: Error) => toastError(`Erreur lors de l'ajout d'une membre: ${error.message}`), + () => toastSuccess("Membre ajoutée !") +) diff --git a/src/store/membreList.ts b/src/store/membreList.ts index f5f6d67..929e727 100644 --- a/src/store/membreList.ts +++ b/src/store/membreList.ts @@ -1,7 +1,6 @@ import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit" -import { toast } from "react-toastify" -import { StateRequest } from "./utils" +import { StateRequest, toastError, elementListFetch } from "./utils" import { Membre, membreListGet } from "../services/membres" import { AppThunk, AppState } from "." @@ -32,26 +31,13 @@ const membreList = createSlice({ export default membreList.reducer export const { getRequesting, getSuccess, getFailure } = membreList.actions -export const fetchMembreList = (): AppThunk => async (dispatch) => { - dispatch(getRequesting()) - - const { error, data } = await membreListGet() - - if (error) { - dispatch(getFailure(error.message)) - toast.error(`Erreur lors du chargement des utilisateurs: ${error.message}`, { - position: "top-center", - autoClose: 6000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - progress: undefined, - }) - } else { - dispatch(getSuccess(data as Membre[])) - } -} +export const fetchMembreList = elementListFetch( + membreListGet, + getRequesting, + getSuccess, + getFailure, + (error: Error) => toastError(`Erreur lors du chargement des membres: ${error.message}`) +) const shouldFetchMembreList = (state: AppState) => state.membreList.readyStatus !== "success" diff --git a/src/store/membreSet.ts b/src/store/membreSet.ts index 7435b4c..e10ffb2 100644 --- a/src/store/membreSet.ts +++ b/src/store/membreSet.ts @@ -1,13 +1,9 @@ import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit" -import { toast } from "react-toastify" -import { StateRequest } from "./utils" +import { StateRequest, toastError, toastSuccess, elementSet } from "./utils" import { Membre, membreSet } from "../services/membres" -import { AppThunk } from "." -const membreAdapter = createEntityAdapter({ - selectId: (membre) => membre.id, -}) +const membreAdapter = createEntityAdapter() const membreSetSlice = createSlice({ name: "membreSet", @@ -20,7 +16,7 @@ const membreSetSlice = createSlice({ }, getSuccess: (state, { payload }: PayloadAction) => { state.readyStatus = "success" - membreAdapter.addOne(state, payload) + membreAdapter.setOne(state, payload) }, getFailure: (state, { payload }: PayloadAction) => { state.readyStatus = "failure" @@ -32,34 +28,11 @@ const membreSetSlice = createSlice({ export default membreSetSlice.reducer export const { getRequesting, getSuccess, getFailure } = membreSetSlice.actions -export const sendMembreSet = - (membre: Membre): AppThunk => - async (dispatch) => { - dispatch(getRequesting()) - - const { error, data } = await membreSet(membre) - - if (error) { - dispatch(getFailure(error.message)) - toast.error(`Erreur lors de la modification d'un membre: ${error.message}`, { - position: "top-center", - autoClose: 6000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - progress: undefined, - }) - } else { - dispatch(getSuccess(data as Membre)) - toast.success("Membre modifié !", { - position: "top-center", - autoClose: 3000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - progress: undefined, - }) - } - } +export const fetchMembreSet = elementSet( + membreSet, + getRequesting, + getSuccess, + getFailure, + (error: Error) => toastError(`Erreur lors de la modification d'un membre: ${error.message}`), + () => toastSuccess("Membre modifié !") +) diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts index a00418c..795f428 100644 --- a/src/store/rootReducer.ts +++ b/src/store/rootReducer.ts @@ -1,18 +1,24 @@ import { History } from "history" import { connectRouter } from "connected-react-router" -import membreList from "./membreList" -import membre from "./membre" -import jeuJavList from "./jeuJavList" +import envieAdd from "./envieAdd" import envieList from "./envieList" +import jeuJavList from "./jeuJavList" +import membre from "./membre" +import membreAdd from "./membreAdd" +import membreList from "./membreList" +import membreSet from "./membreSet" // Use inferred return type for making correctly Redux types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export default (history: History) => ({ - membreList, - membre, - jeuJavList, + envieAdd, envieList, + jeuJavList, + membre, + membreAdd, + membreList, + membreSet, router: connectRouter(history) as any, // Register more reducers... }) diff --git a/src/store/utils.ts b/src/store/utils.ts index 8b57c41..7ba6f82 100644 --- a/src/store/utils.ts +++ b/src/store/utils.ts @@ -1,4 +1,156 @@ +import { ActionCreatorWithoutPayload, ActionCreatorWithPayload } from "@reduxjs/toolkit" +import { toast } from "react-toastify" + +import { AppThunk } from "." + export interface StateRequest { readyStatus: "idle" | "request" | "success" | "failure" error?: string } + +export function toastError(message: string): void { + toast.error(message, { + position: "top-center", + autoClose: 6000, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + }) +} + +export function toastSuccess(message: string): void { + toast.success(message, { + position: "top-center", + autoClose: 3000, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + }) +} + +export function elementFetch( + elementService: (id: number) => Promise<{ + data?: Element | undefined + error?: Error | undefined + }>, + getRequesting: ActionCreatorWithoutPayload, + getSuccess: ActionCreatorWithPayload, + getFailure: ActionCreatorWithPayload, + errorMessage: (error: Error) => void = (_error) => { + /* Meant to be empty */ + }, + successMessage: () => void = () => { + /* Meant to be empty */ + } +): (id: number) => AppThunk { + return (id: number): AppThunk => + async (dispatch) => { + dispatch(getRequesting()) + + const { error, data } = await elementService(id) + + if (error) { + dispatch(getFailure(error.message)) + errorMessage(error) + } else { + dispatch(getSuccess(data as Element)) + successMessage() + } + } +} + +export function elementAddFetch( + elementAddService: (membreWithoutId: Omit) => Promise<{ + data?: Element | undefined + error?: Error | undefined + }>, + getRequesting: ActionCreatorWithoutPayload, + getSuccess: ActionCreatorWithPayload, + getFailure: ActionCreatorWithPayload, + errorMessage: (error: Error) => void = (_error) => { + /* Meant to be empty */ + }, + successMessage: () => void = () => { + /* Meant to be empty */ + } +): (membreWithoutId: Omit) => AppThunk { + return (membreWithoutId: Omit): AppThunk => + async (dispatch) => { + dispatch(getRequesting()) + + const { error, data } = await elementAddService(membreWithoutId) + + if (error) { + dispatch(getFailure(error.message)) + errorMessage(error) + } else { + dispatch(getSuccess(data as Element)) + successMessage() + } + } +} + +export function elementListFetch( + elementListService: () => Promise<{ + data?: Element[] | undefined + error?: Error | undefined + }>, + getRequesting: ActionCreatorWithoutPayload, + getSuccess: ActionCreatorWithPayload, + getFailure: ActionCreatorWithPayload, + errorMessage: (error: Error) => void = (_error) => { + /* Meant to be empty */ + }, + successMessage: () => void = () => { + /* Meant to be empty */ + } +): () => AppThunk { + return (): AppThunk => async (dispatch) => { + dispatch(getRequesting()) + + const { error, data } = await elementListService() + + if (error) { + dispatch(getFailure(error.message)) + errorMessage(error) + } else { + dispatch(getSuccess(data as Element[])) + successMessage() + } + } +} + +export function elementSet( + elementSetService: (membre: Element) => Promise<{ + data?: Element | undefined + error?: Error | undefined + }>, + getRequesting: ActionCreatorWithoutPayload, + getSuccess: ActionCreatorWithPayload, + getFailure: ActionCreatorWithPayload, + errorMessage: (error: Error) => void = (_error) => { + /* Meant to be empty */ + }, + successMessage: () => void = () => { + /* Meant to be empty */ + } +): (element: Element) => AppThunk { + return (element: Element): AppThunk => + async (dispatch) => { + dispatch(getRequesting()) + + const { error, data } = await elementSetService(element) + + if (error) { + dispatch(getFailure(error.message)) + errorMessage(error) + } else { + dispatch(getSuccess(data as Element)) + successMessage() + } + } +}