Add db new line support

This commit is contained in:
pikiou 2022-05-24 17:43:12 +02:00
parent da643df6a6
commit 028d703e98
11 changed files with 124 additions and 29 deletions

View File

@ -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 (
<ul className={styles.list}>
{volunteers.map((volunteer: Volunteer) => (
@ -25,6 +31,12 @@ const DbEdit: FC = (): JSX.Element => {
volunteer={volunteer}
/>
))}
<MemberEdit
key={nextId}
addBefore={addVolunteer}
saveVolunteer={saveVolunteer}
volunteer={nextVolunteer}
/>
</ul>
)
}

View File

@ -10,16 +10,29 @@ import { toastError } from "../../store/utils"
interface Props {
volunteer: Volunteer
saveVolunteer: (newVolunteer: Partial<Volunteer>) => void
addBefore?: () => void
}
const MemberEdit: FC<Props> = ({ volunteer, saveVolunteer }): JSX.Element => {
const MemberEdit: FC<Props> = ({ volunteer, saveVolunteer, addBefore }): JSX.Element => {
const [localVolunteer, setLocalVolunteer] = useState(volunteer)
async function addAndWait() {
if (addBefore) {
addBefore()
await new Promise<void>((resolve) => {
setTimeout(() => resolve(), 1000)
})
}
}
const stringDispatch =
(propName: string) =>
(e: React.ChangeEvent<HTMLInputElement>): void => {
saveVolunteer({ id: localVolunteer.id, [propName]: e.target.value })
setLocalVolunteer({ ...localVolunteer, [propName]: e.target.value })
async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => {
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<Props> = ({ volunteer, saveVolunteer }): JSX.Element => {
const numberDispatch =
(propName: string) =>
(e: React.ChangeEvent<HTMLInputElement>): void => {
const value: number = +e.target.value
async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => {
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<Props> = ({ volunteer, saveVolunteer }): JSX.Element => {
const booleanDispatch =
(propName: string) =>
(e: React.ChangeEvent<HTMLInputElement>): void => {
const value: boolean = e.target.value !== "0" && e.target.value !== ""
saveVolunteer({ id: localVolunteer.id, [propName]: value })
async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => {
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<Props> = ({ volunteer, saveVolunteer }): JSX.Element => {
)
}
MemberEdit.defaultProps = {
addBefore: undefined,
}
export default withUserRole(ROLES.ADMIN, memo(withUserConnected(MemberEdit)))
export const fetchFor = []

View File

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

View File

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

View File

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

View File

@ -19,6 +19,7 @@ export const volunteerDiscordIdGet = serviceAccessors.securedCustomGet<
[number],
VolunteerDiscordId
>("DiscordId")
export const volunteerAddNew = serviceAccessors.securedCustomPost<[]>("AddNew")
export const volunteerPartialAdd = serviceAccessors.customPost<[Partial<Volunteer>]>("PartialAdd")
export const volunteerSet = serviceAccessors.securedCustomPost<[Partial<Volunteer>]>("Set")

View File

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

View File

@ -64,8 +64,8 @@ export function elementFetch<Element, ServiceInput extends Array<any>>(
}
}
export function elementAddFetch<Element>(
elementAddService: (elementWithoutId: Omit<Element, "id">) => Promise<{
export function elementAddFetch<Element, ServiceInput extends Array<any>>(
elementAddService: (...idArgs: ServiceInput) => Promise<{
data?: Element | undefined
error?: Error | undefined
}>,
@ -74,12 +74,12 @@ export function elementAddFetch<Element>(
getFailure: ActionCreatorWithPayload<string, string>,
errorMessage?: (error: Error) => void,
successMessage?: () => void
): (elementWithoutId: Omit<Element, "id">) => AppThunk {
return (elementWithoutId: Omit<Element, "id">): 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))

View File

@ -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<Volunteer>()
const volunteerAddNewSlice = createSlice({
name: "volunteerAddNew",
initialState: volunteerAdapter.getInitialState({
readyStatus: "idle",
} as StateRequest),
reducers: {
getRequesting: (state) => {
state.readyStatus = "request"
},
getSuccess: (state, { payload }: PayloadAction<Volunteer>) => {
state.readyStatus = "success"
volunteerAdapter.setOne(state, payload)
},
getFailure: (state, { payload }: PayloadAction<string>) => {
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))
}

View File

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

View File

@ -41,19 +41,14 @@ export const fetchVolunteerSet = elementFetch(
() => toastSuccess("Bénévole modifié !")
)
const shouldFetchVolunteerSet = (_state: AppState) => true
export const fetchVolunteerSetIfNeed =
(newPartialVolunteer: Partial<Volunteer>): 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
)