Add notifications to home page

This commit is contained in:
pikiou
2022-01-07 14:23:33 +01:00
parent adde4f366e
commit 5cbb5811ec
37 changed files with 661 additions and 215 deletions

View File

@@ -8,25 +8,11 @@ import volunteer, {
fetchVolunteer,
initialState,
} from "../volunteer"
import { Volunteer } from "../../services/volunteers"
import { Volunteer, volunteerExample } from "../../services/volunteers"
jest.mock("axios")
const mockData: Volunteer = {
id: 1,
lastname: "Aupeix",
firstname: "Amélie",
email: "pakouille.lakouille@yahoo.fr",
mobile: "0675650392",
photo: "images/volunteers/$taille/amélie_aupeix.jpg",
food: "Végétarien",
adult: 1,
privileges: 0,
active: 0,
created: new Date(0),
password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
}
const mockData: Volunteer = volunteerExample
const { id } = mockData
const mockError = "Oops! Something went wrong."

View File

@@ -9,27 +9,11 @@ import volunteerList, {
getFailure,
fetchVolunteerList,
} from "../volunteerList"
import { Volunteer } from "../../services/volunteers"
import { Volunteer, volunteerExample } from "../../services/volunteers"
jest.mock("axios")
const mockData: Volunteer[] = [
{
id: 1,
lastname: "Aupeix",
firstname: "Amélie",
email: "pakouille.lakouille@yahoo.fr",
mobile: "0675650392",
photo: "images/volunteers/$taille/amélie_aupeix.jpg",
food: "Végétarien",
adult: 1,
privileges: 0,
active: 0,
created: new Date(0),
password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
},
]
const mockData: Volunteer[] = [volunteerExample]
const mockError = "Oops! Something went wrong."
describe("volunteerList reducer", () => {

35
src/store/auth.ts Normal file
View File

@@ -0,0 +1,35 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit"
import { AppState } from "."
// Define a type for the slice state
interface AuthState {
id: number
jwt: string
}
// Define the initial state using that type
const initialState: AuthState = {
id: 0,
jwt: "",
}
export const auth = createSlice({
name: "auth",
initialState,
reducers: {
setCurrentUser: (state, action: PayloadAction<AuthState>) => {
state.id = action.payload.id
state.jwt = action.payload.jwt
},
logoutUser: (state) => {
state.id = 0
state.jwt = ""
},
},
})
export const { setCurrentUser, logoutUser } = auth.actions
export const selectCount = (state: AppState): AuthState => state.auth
export default auth.reducer

View File

@@ -2,18 +2,22 @@ import { createMemoryHistory, createBrowserHistory } from "history"
import { Action, configureStore, EntityState } from "@reduxjs/toolkit"
import { ThunkAction } from "redux-thunk"
import { routerMiddleware } from "connected-react-router"
import Cookies from "js-cookie"
import createRootReducer from "./rootReducer"
import { StateRequest } from "./utils"
import { setCurrentUser, logoutUser } from "./auth"
interface Arg {
initialState?: typeof window.__INITIAL_STATE__
url?: string
jwt?: string
id?: number
}
// Use inferred return type for making correctly Redux types
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
const createStore = ({ initialState, url }: Arg = {}) => {
const createStore = ({ initialState, url, jwt, id }: Arg = {}) => {
const history = __SERVER__
? createMemoryHistory({ initialEntries: [url || "/"] })
: createBrowserHistory()
@@ -28,10 +32,20 @@ const createStore = ({ initialState, url }: Arg = {}) => {
devTools: __DEV__,
})
if (jwt && id) {
store.dispatch(setCurrentUser({ jwt, id }))
} else {
store.dispatch(logoutUser())
}
return { store, history }
}
const { store } = createStore()
const storage: any = localStorage
const id = +(Cookies.get("id") || storage?.getItem("id"))
const jwt = Cookies.get("jwt") || storage?.getItem("jwt")
const { store } = createStore({ id, jwt })
export type AppState = ReturnType<typeof store.getState>

View File

@@ -1,6 +1,7 @@
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"
@@ -10,12 +11,14 @@ import volunteerList from "./volunteerList"
import volunteerSet from "./volunteerSet"
import volunteerLogin from "./volunteerLogin"
import volunteerForgot from "./volunteerForgot"
import volunteerNotifsSet from "./volunteerNotifsSet"
import preVolunteerAdd from "./preVolunteerAdd"
import preVolunteerCount from "./preVolunteerCount"
// 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,
@@ -25,6 +28,7 @@ export default (history: History) => ({
volunteerSet,
volunteerLogin,
volunteerForgot,
volunteerNotifsSet,
preVolunteerAdd,
preVolunteerCount,
router: connectRouter(history) as any,

View File

@@ -1,7 +1,7 @@
import { ActionCreatorWithoutPayload, ActionCreatorWithPayload } from "@reduxjs/toolkit"
import { toast } from "react-toastify"
import { AppThunk } from "."
import { AppThunk, AppDispatch } from "."
export interface StateRequest {
readyStatus: "idle" | "request" | "success" | "failure"
@@ -32,8 +32,8 @@ export function toastSuccess(message: string): void {
})
}
export function elementFetch<Element>(
elementService: (...idArgs: any[]) => Promise<{
export function elementFetch<Element, ServiceInput extends Array<any>>(
elementService: (...idArgs: ServiceInput) => Promise<{
data?: Element | undefined
error?: Error | undefined
}>,
@@ -41,9 +41,9 @@ export function elementFetch<Element>(
getSuccess: ActionCreatorWithPayload<Element, string>,
getFailure: ActionCreatorWithPayload<string, string>,
errorMessage?: (error: Error) => void,
successMessage?: (data: Element) => void
): (...idArgs: any[]) => AppThunk {
return (...idArgs: any[]): AppThunk =>
successMessage?: (data: Element, dispatch: AppDispatch) => void
): (...idArgs: ServiceInput) => AppThunk {
return (...idArgs: ServiceInput): AppThunk =>
async (dispatch) => {
dispatch(getRequesting())
@@ -54,7 +54,7 @@ export function elementFetch<Element>(
errorMessage?.(error)
} else {
dispatch(getSuccess(data as Element))
successMessage?.(data as Element)
successMessage?.(data as Element, dispatch)
}
}
}

View File

@@ -3,6 +3,9 @@ import { PayloadAction, createSlice } from "@reduxjs/toolkit"
import { StateRequest, elementFetch } from "./utils"
import { VolunteerLogin, volunteerLogin } from "../services/volunteers"
import { setJWT } from "../services/auth"
import { AppDispatch } from "."
import { setCurrentUser } from "./auth"
import { fetchVolunteerNotifsSet } from "./volunteerNotifsSet"
type StateVolunteer = { entity?: VolunteerLogin } & StateRequest
@@ -31,13 +34,15 @@ const volunteerLoginSlice = createSlice({
export default volunteerLoginSlice.reducer
export const { getRequesting, getSuccess, getFailure } = volunteerLoginSlice.actions
export const fetchVolunteerLogin = elementFetch(
export const fetchVolunteerLogin = elementFetch<VolunteerLogin, Parameters<typeof volunteerLogin>>(
volunteerLogin,
getRequesting,
getSuccess,
getFailure,
undefined,
(login: VolunteerLogin) => {
setJWT(login.jwt)
(login: VolunteerLogin, dispatch: AppDispatch) => {
setJWT(login.jwt, login.id)
dispatch(setCurrentUser(login))
dispatch(fetchVolunteerNotifsSet(login.jwt, login.id, {}))
}
)

View File

@@ -0,0 +1,57 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit"
import { StateRequest, toastError, elementFetch } from "./utils"
import { VolunteerNotifs, volunteerNotifsSet } from "../services/volunteers"
import { AppThunk, AppState } from "."
type StateVolunteerNotifsSet = { entity?: VolunteerNotifs } & StateRequest
export const initialState: StateVolunteerNotifsSet = {
readyStatus: "idle",
}
const volunteerNotifsSetSlice = createSlice({
name: "volunteerNotifsSet",
initialState,
reducers: {
getRequesting: (_) => ({
readyStatus: "request",
}),
getSuccess: (_, { payload }: PayloadAction<VolunteerNotifs>) => ({
readyStatus: "success",
entity: payload,
}),
getFailure: (_, { payload }: PayloadAction<string>) => ({
readyStatus: "failure",
error: payload,
}),
},
})
export default volunteerNotifsSetSlice.reducer
export const { getRequesting, getSuccess, getFailure } = volunteerNotifsSetSlice.actions
export const fetchVolunteerNotifsSet = elementFetch(
volunteerNotifsSet,
getRequesting,
getSuccess,
getFailure,
(error: Error) => toastError(`Erreur lors du chargement des notifications: ${error.message}`)
)
const shouldFetchVolunteerNotifsSet = (state: AppState, id: number) =>
state.volunteerNotifsSet.readyStatus !== "success" ||
(state.volunteerNotifsSet.entity && state.volunteerNotifsSet.entity.id !== id)
export const fetchVolunteerNotifsSetIfNeed =
(id = 0, notif: Partial<VolunteerNotifs> = {}): AppThunk =>
(dispatch, getState) => {
let jwt = ""
if (!id) {
;({ id, jwt } = getState().auth)
}
if (shouldFetchVolunteerNotifsSet(getState(), id))
return dispatch(fetchVolunteerNotifsSet(jwt, id, notif))
return null
}