diff --git a/src/components/Admin/DbEdit.tsx b/src/components/Admin/DbEdit.tsx index e4fbbea..4256440 100644 --- a/src/components/Admin/DbEdit.tsx +++ b/src/components/Admin/DbEdit.tsx @@ -1,3 +1,4 @@ +import { max } from "lodash" import { FC, memo } from "react" import { useSelector } from "react-redux" import withUserConnected from "../../utils/withUserConnected" @@ -9,13 +10,18 @@ import useAction from "../../utils/useAction" import { fetchVolunteerSetIfNeed } from "../../store/volunteerSet" import { Volunteer } from "../../services/volunteers" import styles from "./styles.module.scss" +import { fetchVolunteerAddNewIfNeed } from "../../store/volunteerAddNew" const DbEdit: FC = (): JSX.Element => { const volunteers = useSelector(selectVolunteerList) const saveVolunteer = useAction(fetchVolunteerSetIfNeed) + const addVolunteer = useAction(fetchVolunteerAddNewIfNeed) if (!volunteers) { return <>No member found } + const nextId = (max(volunteers.map((v) => v.id)) || 0) + 1 + const nextVolunteer = new Volunteer() + nextVolunteer.id = nextId return ( ) } diff --git a/src/components/Admin/MemberEdit.tsx b/src/components/Admin/MemberEdit.tsx index ee20498..0a538a9 100644 --- a/src/components/Admin/MemberEdit.tsx +++ b/src/components/Admin/MemberEdit.tsx @@ -10,16 +10,29 @@ import { toastError } from "../../store/utils" interface Props { volunteer: Volunteer saveVolunteer: (newVolunteer: Partial) => void + addBefore?: () => void } -const MemberEdit: FC = ({ volunteer, saveVolunteer }): JSX.Element => { +const MemberEdit: FC = ({ volunteer, saveVolunteer, addBefore }): JSX.Element => { const [localVolunteer, setLocalVolunteer] = useState(volunteer) + async function addAndWait() { + if (addBefore) { + addBefore() + await new Promise((resolve) => { + setTimeout(() => resolve(), 1000) + }) + } + } + const stringDispatch = (propName: string) => - (e: React.ChangeEvent): void => { - saveVolunteer({ id: localVolunteer.id, [propName]: e.target.value }) - setLocalVolunteer({ ...localVolunteer, [propName]: e.target.value }) + async (e: React.ChangeEvent): Promise => { + const rawValue = e.target.value + const value = rawValue + await addAndWait() + saveVolunteer({ id: localVolunteer.id, [propName]: rawValue }) + setLocalVolunteer({ ...localVolunteer, [propName]: value }) } function stringInput(id: string, value: string): JSX.Element { @@ -40,13 +53,15 @@ const MemberEdit: FC = ({ volunteer, saveVolunteer }): JSX.Element => { const numberDispatch = (propName: string) => - (e: React.ChangeEvent): void => { - const value: number = +e.target.value + async (e: React.ChangeEvent): Promise => { + const rawValue = e.target.value + const value: number = +rawValue if (!isFinite(value)) { toastError("Should be a number") return } - saveVolunteer({ id: localVolunteer.id, [propName]: +value }) + await addAndWait() + saveVolunteer({ id: localVolunteer.id, [propName]: rawValue }) setLocalVolunteer({ ...localVolunteer, [propName]: +value }) } @@ -68,9 +83,11 @@ const MemberEdit: FC = ({ volunteer, saveVolunteer }): JSX.Element => { const booleanDispatch = (propName: string) => - (e: React.ChangeEvent): void => { - const value: boolean = e.target.value !== "0" && e.target.value !== "" - saveVolunteer({ id: localVolunteer.id, [propName]: value }) + async (e: React.ChangeEvent): Promise => { + const rawValue = e.target.value + const value: boolean = rawValue !== "0" && rawValue !== "" + await addAndWait() + saveVolunteer({ id: localVolunteer.id, [propName]: rawValue }) setLocalVolunteer({ ...localVolunteer, [propName]: value }) } @@ -112,6 +129,9 @@ const MemberEdit: FC = ({ volunteer, saveVolunteer }): JSX.Element => { ) } +MemberEdit.defaultProps = { + addBefore: undefined, +} export default withUserRole(ROLES.ADMIN, memo(withUserConnected(MemberEdit))) export const fetchFor = [] diff --git a/src/components/RegisterForm/index.tsx b/src/components/RegisterForm/index.tsx index 984823c..260f49d 100644 --- a/src/components/RegisterForm/index.tsx +++ b/src/components/RegisterForm/index.tsx @@ -133,7 +133,7 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => { ) const { error: volunteerError, entities: volunteer } = useSelector( - (state: AppState) => state.volunteerAdd, + (state: AppState) => state.volunteerPartialAdd, shallowEqual ) diff --git a/src/server/gsheets/volunteers.ts b/src/server/gsheets/volunteers.ts index 5349c28..536f6ec 100644 --- a/src/server/gsheets/volunteers.ts +++ b/src/server/gsheets/volunteers.ts @@ -1,4 +1,4 @@ -import { assign, cloneDeep, omit, pick } from "lodash" +import { assign, cloneDeep, max, omit, pick } from "lodash" import bcrypt from "bcrypt" import sgMail from "@sendgrid/mail" @@ -31,6 +31,26 @@ export const volunteerListGet = expressAccessor.get(async (list, _body, id) => { return list }) +export const volunteerAddNew = expressAccessor.add(async (list, _body, _id, roles) => { + if (!roles.includes("admin")) { + throw Error(`À moins d'être admin, on ne peut pas modifier n'importe quel utilisateur`) + } + const id = (max(list.map((v) => v.id)) || 0) + 1 + const password = generatePassword() + const passwordHash = await bcrypt.hash(password, 10) + + const newVolunteer: Volunteer = new Volunteer() + newVolunteer.id = id + newVolunteer.password1 = passwordHash + newVolunteer.password2 = passwordHash + newVolunteer.firstname = password + + return { + toDatabase: newVolunteer, + toCaller: newVolunteer, + } +}) + export const volunteerSet = expressAccessor.set(async (list, body, _id, roles) => { if (!roles.includes("admin")) { throw Error(`À moins d'être admin, on ne peut pas modifier n'importe quel utilisateur`) diff --git a/src/server/index.ts b/src/server/index.ts index c5ecdce..e264aeb 100755 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -35,6 +35,7 @@ import { volunteerTeamAssignSet, volunteerListGet, volunteerKnowledgeSet, + volunteerAddNew, } from "./gsheets/volunteers" import { wishListGet, wishAdd } from "./gsheets/wishes" import config from "../config" @@ -114,6 +115,7 @@ app.post("/VolunteerTeamWishesSet", secure as RequestHandler, volunteerTeamWishe app.post("/VolunteerTeamAssignSet", secure as RequestHandler, volunteerTeamAssignSet) // Admin only +app.post("/VolunteerAddNew", secure as RequestHandler, volunteerAddNew) app.post("/VolunteerSet", secure as RequestHandler, volunteerSet) app.get("/GameDetailsUpdate", secure as RequestHandler, gameDetailsUpdate) diff --git a/src/services/volunteersAccessors.ts b/src/services/volunteersAccessors.ts index 194943e..dea12eb 100644 --- a/src/services/volunteersAccessors.ts +++ b/src/services/volunteersAccessors.ts @@ -19,6 +19,7 @@ export const volunteerDiscordIdGet = serviceAccessors.securedCustomGet< [number], VolunteerDiscordId >("DiscordId") +export const volunteerAddNew = serviceAccessors.securedCustomPost<[]>("AddNew") export const volunteerPartialAdd = serviceAccessors.customPost<[Partial]>("PartialAdd") export const volunteerSet = serviceAccessors.securedCustomPost<[Partial]>("Set") diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts index da91969..b05d40c 100644 --- a/src/store/rootReducer.ts +++ b/src/store/rootReducer.ts @@ -11,7 +11,7 @@ import miscMeetingDateList from "./miscMeetingDateList" import postulantAdd from "./postulantAdd" import teamList from "./teamList" import ui from "./ui" -import volunteerAdd from "./volunteerPartialAdd" +import volunteerPartialAdd from "./volunteerPartialAdd" import volunteerAsksSet from "./volunteerAsksSet" import volunteerDayWishesSet from "./volunteerDayWishesSet" import volunteerDiscordId from "./volunteerDiscordId" @@ -39,7 +39,7 @@ export default (history: History) => ({ postulantAdd, teamList, ui, - volunteerAdd, + volunteerPartialAdd, volunteerAsksSet, volunteerDayWishesSet, volunteerDiscordId, diff --git a/src/store/utils.ts b/src/store/utils.ts index 395afba..6d29f03 100644 --- a/src/store/utils.ts +++ b/src/store/utils.ts @@ -64,8 +64,8 @@ export function elementFetch>( } } -export function elementAddFetch( - elementAddService: (elementWithoutId: Omit) => Promise<{ +export function elementAddFetch>( + elementAddService: (...idArgs: ServiceInput) => Promise<{ data?: Element | undefined error?: Error | undefined }>, @@ -74,12 +74,12 @@ export function elementAddFetch( getFailure: ActionCreatorWithPayload, errorMessage?: (error: Error) => void, successMessage?: () => void -): (elementWithoutId: Omit) => AppThunk { - return (elementWithoutId: Omit): AppThunk => +): (...idArgs: ServiceInput) => AppThunk { + return (...idArgs: ServiceInput): AppThunk => async (dispatch) => { dispatch(getRequesting()) - const { error, data } = await elementAddService(elementWithoutId) + const { error, data } = await elementAddService(...idArgs) if (error) { dispatch(getFailure(error.message)) diff --git a/src/store/volunteerAddNew.ts b/src/store/volunteerAddNew.ts new file mode 100644 index 0000000..7a952bb --- /dev/null +++ b/src/store/volunteerAddNew.ts @@ -0,0 +1,45 @@ +import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit" + +import { StateRequest, elementAddFetch, toastError } from "./utils" +import { Volunteer } from "../services/volunteers" +import { volunteerAddNew } from "../services/volunteersAccessors" +import { AppThunk } from "." + +const volunteerAdapter = createEntityAdapter() + +const volunteerAddNewSlice = createSlice({ + name: "volunteerAddNew", + initialState: volunteerAdapter.getInitialState({ + readyStatus: "idle", + } as StateRequest), + reducers: { + getRequesting: (state) => { + state.readyStatus = "request" + }, + getSuccess: (state, { payload }: PayloadAction) => { + state.readyStatus = "success" + volunteerAdapter.setOne(state, payload) + }, + getFailure: (state, { payload }: PayloadAction) => { + state.readyStatus = "failure" + state.error = payload + }, + }, +}) + +export default volunteerAddNewSlice.reducer +export const { getRequesting, getSuccess, getFailure } = volunteerAddNewSlice.actions + +export const fetchVolunteerAddNew = elementAddFetch( + volunteerAddNew, + getRequesting, + getSuccess, + getFailure, + () => toastError("Erreur d'ajout !"), + () => null +) + +export const fetchVolunteerAddNewIfNeed = (): AppThunk => (dispatch, getState) => { + const { jwt } = getState().auth + return dispatch(fetchVolunteerAddNew(jwt)) +} diff --git a/src/store/volunteerPartialAdd.ts b/src/store/volunteerPartialAdd.ts index a26ef63..9e1ca61 100644 --- a/src/store/volunteerPartialAdd.ts +++ b/src/store/volunteerPartialAdd.ts @@ -1,13 +1,13 @@ import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit" -import { StateRequest, elementAddFetch } from "./utils" +import { StateRequest, elementAddFetch, toastError } from "./utils" import { Volunteer } from "../services/volunteers" import { volunteerPartialAdd } from "../services/volunteersAccessors" const volunteerAdapter = createEntityAdapter() const volunteerPartialAddSlice = createSlice({ - name: "volunteerAdd", + name: "volunteerPartialAdd", initialState: volunteerAdapter.getInitialState({ readyStatus: "idle", } as StateRequest), @@ -34,6 +34,6 @@ export const fetchVolunteerPartialAdd = elementAddFetch( getRequesting, getSuccess, getFailure, - () => null, + () => toastError("Erreur d'inscription !"), () => null ) diff --git a/src/store/volunteerSet.ts b/src/store/volunteerSet.ts index 949f827..a4e88da 100644 --- a/src/store/volunteerSet.ts +++ b/src/store/volunteerSet.ts @@ -41,19 +41,14 @@ export const fetchVolunteerSet = elementFetch( () => toastSuccess("Bénévole modifié !") ) -const shouldFetchVolunteerSet = (_state: AppState) => true - export const fetchVolunteerSetIfNeed = (newPartialVolunteer: Partial): AppThunk => (dispatch, getState) => { const { jwt } = getState().auth - if (shouldFetchVolunteerSet(getState())) - return dispatch(fetchVolunteerSet(jwt, newPartialVolunteer)) - - return null + return dispatch(fetchVolunteerSet(jwt, newPartialVolunteer)) } export const selectVolunteerSet = createSelector( (state: AppState) => state, - (state): string | undefined => state.volunteerSet?.entity?.discordId + (state): Volunteer | undefined => state.volunteerSet?.entity )