Factors redux tools to access the DB

This commit is contained in:
forceoranj 2021-11-16 03:02:03 +01:00
parent b76fbc78ff
commit bf62510d0a
24 changed files with 506 additions and 462 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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<Element extends ElementWithId>(
export async function listGet<Element extends ElementWithId>(
sheetName: string,
specimen: Element
): Promise<Element[]> {
@ -38,17 +37,15 @@ export async function getList<Element extends ElementWithId>(
return elements
}
// eslint-disable-next-line @typescript-eslint/ban-types
export async function get<Element extends ElementWithId>(
sheetName: string,
membreId: number,
specimen: Element
): Promise<Element | undefined> {
const list = await getList<Element>(sheetName, specimen)
const list = await listGet<Element>(sheetName, specimen)
return list.find((element) => element.id === membreId)
}
// eslint-disable-next-line @typescript-eslint/ban-types
export async function setList<Element extends ElementWithId>(
sheetName: string,
elements: Element[]
@ -101,7 +98,6 @@ export async function setList<Element extends ElementWithId>(
return true
}
// eslint-disable-next-line @typescript-eslint/ban-types
export async function set<Element extends ElementWithId>(
sheetName: string,
element: Element

View File

@ -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<void> => {
try {
const list = await getList<Envie>("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<void> => {
try {
const envie = await add<EnvieWithoutId, Envie>("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<EnvieWithoutId, Envie>(sheetName)
export const envieSet = setRequest(sheetName)

View File

@ -0,0 +1,61 @@
import { Request, Response, NextFunction } from "express"
import { ElementWithId, get, listGet, add, set } from "./accessors"
export function getRequest<Element extends { id: number }>(sheetName: string, specimen: Element) {
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
try {
const id = parseInt(request.query.id as string, 10) || -1
const elements = await get<Element>(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<ElementNoId extends object, Element extends ElementNoId & ElementWithId>(
sheetName: string
) {
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
try {
const element = await add<ElementNoId, Element>(sheetName, request.body)
if (element) {
response.status(200).json(element)
}
} catch (e: unknown) {
response.status(400).json(e)
}
}
}
export function listGetRequest<Element extends { id: number }>(
sheetName: string,
specimen: Element
) {
return async (_request: Request, response: Response, _next: NextFunction): Promise<void> => {
try {
const elements = await listGet<Element>(sheetName, specimen)
if (elements) {
response.status(200).json(elements)
}
} catch (e: unknown) {
response.status(400).json(e)
}
}
}
export function setRequest<Element extends { id: number }>(sheetName: string) {
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
try {
const element = await set<Element>(sheetName, request.body)
if (element) {
response.status(200).json(element)
}
} catch (e: unknown) {
response.status(400).json(e)
}
}
}

View File

@ -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<void> => {
try {
const list = await getList<JeuJav>("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<void> => {
const list = await getList<JeuJav>("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<JeuJavWithoutId, JeuJav>(sheetName)
export const jeuJavSet = setRequest(sheetName)

View File

@ -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<void> => {
try {
const list = await getList<Membre>("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<void> => {
try {
const id = parseInt(request.query.id as string, 10) || -1
const membre = await get<Membre>("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<void> => {
try {
const envie = await set<Membre>("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<void> => {
try {
const membre = await add<MembreWithoutId, Membre>("Membres", request.body)
if (membre) {
response.status(200).json(membre)
}
} catch (e: unknown) {
response.status(400).json(e)
}
}
export const membreAdd = addRequest<MembreWithoutId, Membre>(sheetName)
export const membreSet = setRequest(sheetName)

View File

@ -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)

View File

@ -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("<MembrePage />", () => {
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", () => {

85
src/services/accessors.ts Normal file
View File

@ -0,0 +1,85 @@
import axios from "axios"
import config from "../config"
export type ElementWithId = unknown & { id: number }
export function get<Element>(elementName: string): (id: number) => Promise<{
data?: Element
error?: Error
}> {
interface ElementGetResponse {
data?: Element
error?: Error
}
return async (id: number): Promise<ElementGetResponse> => {
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<Element>(elementName: string): () => Promise<{
data?: Element[]
error?: Error
}> {
interface ElementListGetResponse {
data?: Element[]
error?: Error
}
return async (): Promise<ElementListGetResponse> => {
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<ElementNoId extends object, Element extends ElementNoId & ElementWithId>(
elementName: string
): (membreWithoutId: ElementNoId) => Promise<{
data?: Element
error?: Error
}> {
interface ElementGetResponse {
data?: Element
error?: Error
}
return async (membreWithoutId: ElementNoId): Promise<ElementGetResponse> => {
try {
const { data } = await axios.post(
`${config.API_URL}/${elementName}Add`,
membreWithoutId
)
return { data }
} catch (error) {
return { error: error as Error }
}
}
}
export function set<Element>(elementName: string): (membre: Element) => Promise<{
data?: Element
error?: Error
}> {
interface ElementGetResponse {
data?: Element
error?: Error
}
return async (membre: Element): Promise<ElementGetResponse> => {
try {
const { data } = await axios.post(`${config.API_URL}/${elementName}Set`, membre)
return { data }
} catch (error) {
return { error: error as Error }
}
}
}

View File

@ -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<Envie, "id">
export interface EnvieListGetResponse {
data?: Envie[]
error?: Error
}
export const envieListGet = async (): Promise<EnvieListGetResponse> => {
try {
const { data } = await axios.get(`${config.API_URL}/EnvieListGet`)
return { data }
} catch (error) {
return { error: error as Error }
}
}
export const envieGet = get<Envie>("Envie")
export interface EnvieAddResponse {
data?: Envie
error?: Error
}
export const envieAdd = async (envieWithoutId: EnvieWithoutId): Promise<EnvieAddResponse> => {
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>("Envie")
export const envieAdd = add<EnvieWithoutId, Envie>("Envie")
export const envieSet = set<Envie>("Envie")

View File

@ -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<JeuJavListResponse> => {
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<JeuJavResponse> => {
try {
const { data } = await axios.get(`${config.API_URL}/JeuJavGet`, { params: { id } })
return { data }
} catch (error) {
return { error: error as Error }
}
}

43
src/services/jeuxJav.ts Normal file
View File

@ -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<JeuJav, "id">
export const jeuJavGet = get<JeuJav>("JeuJav")
export const jeuJavListGet = listGet<JeuJav>("JeuJav")
export const jeuJavAdd = add<JeuJavWithoutId, JeuJav>("JeuJav")
export const jeuJavSet = set<JeuJav>("JeuJav")

View File

@ -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<Membre, "id">
export interface MembreListGetResponse {
data?: Membre[]
error?: Error
}
export const membreListGet = async (): Promise<MembreListGetResponse> => {
try {
const { data } = await axios.get(`${config.API_URL}/MembreListGet`)
return { data }
} catch (error) {
return { error: error as Error }
}
}
export const membreGet = get<Membre>("Membre")
export interface MembreGetResponse {
data?: Membre
error?: Error
}
export const membreGet = async (id: number): Promise<MembreGetResponse> => {
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>("Membre")
export interface MembreSetResponse {
data?: Membre
error?: Error
}
export const membreSet = async (membre: Membre): Promise<MembreSetResponse> => {
try {
const { data } = await axios.post(`${config.API_URL}/MembreSet`, membre)
return { data }
} catch (error) {
return { error: error as Error }
}
}
export const membreAdd = add<MembreWithoutId, Membre>("Membre")
export interface MembreAddResponse {
data?: Membre
error?: Error
}
export const membreAdd = async (membreWithoutId: MembreWithoutId): Promise<MembreAddResponse> => {
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>("Membre")

View File

@ -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)
})
})

View File

@ -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<Envie>({
selectId: (envie) => envie.id,
})
const envieAdapter = createEntityAdapter<Envie>()
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 !")
)

View File

@ -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<Envie>({
selectId: (envie) => envie.id,
})
const envieAdapter = createEntityAdapter<Envie>()
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"

View File

@ -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<JeuJav>({
selectId: (jeuJav) => jeuJav.id,
})
const jeuJavAdapter = createEntityAdapter<JeuJav>()
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"

View File

@ -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
}

38
src/store/membreAdd.ts Normal file
View File

@ -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<Membre>()
const membreAddSlice = createSlice({
name: "addMembre",
initialState: membreAdapter.getInitialState({
readyStatus: "idle",
} as StateRequest),
reducers: {
getRequesting: (state) => {
state.readyStatus = "request"
},
getSuccess: (state, { payload }: PayloadAction<Membre>) => {
state.readyStatus = "success"
membreAdapter.addOne(state, payload)
},
getFailure: (state, { payload }: PayloadAction<string>) => {
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 !")
)

View File

@ -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"

View File

@ -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<Membre>({
selectId: (membre) => membre.id,
})
const membreAdapter = createEntityAdapter<Membre>()
const membreSetSlice = createSlice({
name: "membreSet",
@ -20,7 +16,7 @@ const membreSetSlice = createSlice({
},
getSuccess: (state, { payload }: PayloadAction<Membre>) => {
state.readyStatus = "success"
membreAdapter.addOne(state, payload)
membreAdapter.setOne(state, payload)
},
getFailure: (state, { payload }: PayloadAction<string>) => {
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é !")
)

View File

@ -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...
})

View File

@ -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<Element>(
elementService: (id: number) => Promise<{
data?: Element | undefined
error?: Error | undefined
}>,
getRequesting: ActionCreatorWithoutPayload<string>,
getSuccess: ActionCreatorWithPayload<Element, string>,
getFailure: ActionCreatorWithPayload<string, string>,
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<Element>(
elementAddService: (membreWithoutId: Omit<Element, "id">) => Promise<{
data?: Element | undefined
error?: Error | undefined
}>,
getRequesting: ActionCreatorWithoutPayload<string>,
getSuccess: ActionCreatorWithPayload<Element, string>,
getFailure: ActionCreatorWithPayload<string, string>,
errorMessage: (error: Error) => void = (_error) => {
/* Meant to be empty */
},
successMessage: () => void = () => {
/* Meant to be empty */
}
): (membreWithoutId: Omit<Element, "id">) => AppThunk {
return (membreWithoutId: Omit<Element, "id">): 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<Element>(
elementListService: () => Promise<{
data?: Element[] | undefined
error?: Error | undefined
}>,
getRequesting: ActionCreatorWithoutPayload<string>,
getSuccess: ActionCreatorWithPayload<Element[], string>,
getFailure: ActionCreatorWithPayload<string, string>,
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<Element>(
elementSetService: (membre: Element) => Promise<{
data?: Element | undefined
error?: Error | undefined
}>,
getRequesting: ActionCreatorWithoutPayload<string>,
getSuccess: ActionCreatorWithPayload<Element, string>,
getFailure: ActionCreatorWithPayload<string, string>,
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()
}
}
}