mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-06-09 17:14:21 +02:00
Add password reset
This commit is contained in:
parent
395955f32a
commit
adde4f366e
@ -57,5 +57,6 @@ module.exports = {
|
|||||||
__DEV__: true,
|
__DEV__: true,
|
||||||
__LOCAL__: false,
|
__LOCAL__: false,
|
||||||
__TEST__: false,
|
__TEST__: false,
|
||||||
|
__SENDGRID_API_KEY__: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ module.exports = {
|
|||||||
__SERVER__: false,
|
__SERVER__: false,
|
||||||
__LOCAL__: false,
|
__LOCAL__: false,
|
||||||
__TEST__: true,
|
__TEST__: true,
|
||||||
|
__SENDGRID_API_KEY__: "",
|
||||||
localStorage: { getItem: () => null, setItem: () => null, removeItem: () => null },
|
localStorage: { getItem: () => null, setItem: () => null, removeItem: () => null },
|
||||||
},
|
},
|
||||||
maxConcurrency: 50,
|
maxConcurrency: 50,
|
||||||
|
@ -73,6 +73,7 @@
|
|||||||
"@loadable/component": "^5.15.0",
|
"@loadable/component": "^5.15.0",
|
||||||
"@loadable/server": "^5.15.0",
|
"@loadable/server": "^5.15.0",
|
||||||
"@reduxjs/toolkit": "^1.6.0",
|
"@reduxjs/toolkit": "^1.6.0",
|
||||||
|
"@sendgrid/mail": "^7.6.0",
|
||||||
"@types/lodash": "^4.14.177",
|
"@types/lodash": "^4.14.177",
|
||||||
"autoprefixer": "^10.2.6",
|
"autoprefixer": "^10.2.6",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
|
48
src/components/ForgotForm/ForgotForm.tsx
Normal file
48
src/components/ForgotForm/ForgotForm.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import React, { memo, useCallback } from "react"
|
||||||
|
import { Link } from "react-router-dom"
|
||||||
|
import { AppDispatch } from "../../store"
|
||||||
|
import { fetchVolunteerForgot } from "../../store/volunteerForgot"
|
||||||
|
import styles from "./styles.module.scss"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
dispatch: AppDispatch
|
||||||
|
error: string
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ForgotForm = ({ dispatch, error, message }: Props): JSX.Element => {
|
||||||
|
const onSubmit = useCallback(
|
||||||
|
(event: React.SyntheticEvent): void => {
|
||||||
|
event.preventDefault()
|
||||||
|
const target = event.target as typeof event.target & {
|
||||||
|
email: { value: string }
|
||||||
|
}
|
||||||
|
const email = target.email.value
|
||||||
|
|
||||||
|
dispatch(fetchVolunteerForgot({ email }))
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={onSubmit}>
|
||||||
|
<div className={styles.forgotIntro} key="forgot-intro">
|
||||||
|
Nous allons te renvoyer un mot de passe à l'adresse suivante.
|
||||||
|
</div>
|
||||||
|
<div className={styles.formLine} key="line-email">
|
||||||
|
<label htmlFor="email">Email</label>
|
||||||
|
<input type="email" id="email" name="utilisateur" />
|
||||||
|
</div>
|
||||||
|
<div className={styles.formButtons}>
|
||||||
|
<button type="submit">Connexion</button>
|
||||||
|
</div>
|
||||||
|
<div className={styles.error}>{error}</div>
|
||||||
|
<div className={styles.message}>{message}</div>
|
||||||
|
<div className={styles.link}>
|
||||||
|
<Link to="/login"> S'identifier </Link>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(ForgotForm)
|
43
src/components/ForgotForm/styles.module.scss
Executable file
43
src/components/ForgotForm/styles.module.scss
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
@import "../../theme/variables";
|
||||||
|
@import "../../theme/mixins";
|
||||||
|
|
||||||
|
.forgotIntro {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formLine {
|
||||||
|
padding: 5px 0;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.formButtons {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 5px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin-top: 10px;
|
||||||
|
color: rgb(11, 138, 0);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
margin-top: 10px;
|
||||||
|
color: rgb(255, 0, 0);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import React, { memo, useCallback } from "react"
|
import React, { memo, useCallback } from "react"
|
||||||
import styles from "./styles.module.scss"
|
import { Link } from "react-router-dom"
|
||||||
import { AppDispatch } from "../../store"
|
import { AppDispatch } from "../../store"
|
||||||
import { fetchVolunteerLogin } from "../../store/volunteerLogin"
|
import { fetchVolunteerLogin } from "../../store/volunteerLogin"
|
||||||
|
import styles from "./styles.module.scss"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
dispatch: AppDispatch
|
dispatch: AppDispatch
|
||||||
@ -41,6 +42,9 @@ const LoginForm = ({ dispatch, error }: Props): JSX.Element => {
|
|||||||
<button type="submit">Connexion</button>
|
<button type="submit">Connexion</button>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.error}>{error}</div>
|
<div className={styles.error}>{error}</div>
|
||||||
|
<div className={styles.link}>
|
||||||
|
<Link to="/forgot"> Demander un nouveau mot de passe </Link>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -26,5 +26,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
|
margin-top: 10px;
|
||||||
color: rgb(255, 0, 0);
|
color: rgb(255, 0, 0);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
@ -22,9 +22,9 @@ describe("<VolunteerInfo />", () => {
|
|||||||
adult: 1,
|
adult: 1,
|
||||||
privileges: 0,
|
privileges: 0,
|
||||||
active: 0,
|
active: 0,
|
||||||
comment: "",
|
created: new Date(0),
|
||||||
timestamp: new Date(0),
|
password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
|
||||||
password: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
|
password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
|
@ -23,9 +23,10 @@ describe("<VolunteerList />", () => {
|
|||||||
adult: 1,
|
adult: 1,
|
||||||
privileges: 0,
|
privileges: 0,
|
||||||
active: 0,
|
active: 0,
|
||||||
comment: "",
|
created: new Date(0),
|
||||||
timestamp: new Date(0),
|
password1:
|
||||||
password:
|
"$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
|
||||||
|
password2:
|
||||||
"$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
|
"$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
@ -24,9 +24,9 @@ describe("<SetVolunteer />", () => {
|
|||||||
adult: 1,
|
adult: 1,
|
||||||
privileges: 0,
|
privileges: 0,
|
||||||
active: 0,
|
active: 0,
|
||||||
comment: "",
|
created: new Date(0),
|
||||||
timestamp: new Date(0),
|
password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
|
||||||
password: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
|
password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
|
34
src/pages/Forgot/ForgotPage.tsx
Normal file
34
src/pages/Forgot/ForgotPage.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { RouteComponentProps } from "react-router-dom"
|
||||||
|
import { useDispatch, useSelector, shallowEqual } from "react-redux"
|
||||||
|
import React, { memo } from "react"
|
||||||
|
import { Helmet } from "react-helmet"
|
||||||
|
|
||||||
|
import { AppState } from "../../store"
|
||||||
|
import ForgotForm from "../../components/ForgotForm/ForgotForm"
|
||||||
|
import styles from "./styles.module.scss"
|
||||||
|
|
||||||
|
export type Props = RouteComponentProps
|
||||||
|
|
||||||
|
const ForgotPage: React.FC<Props> = (): JSX.Element => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const forgotError = useSelector((state: AppState) => state.volunteerForgot.error, shallowEqual)
|
||||||
|
const forgotMessage = useSelector(
|
||||||
|
(state: AppState) => state.volunteerForgot.entity?.message,
|
||||||
|
shallowEqual
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.forgotPage}>
|
||||||
|
<div className={styles.forgotContent}>
|
||||||
|
<Helmet title="ForgotPage" />
|
||||||
|
<ForgotForm
|
||||||
|
dispatch={dispatch}
|
||||||
|
error={forgotError || ""}
|
||||||
|
message={forgotMessage || ""}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(ForgotPage)
|
14
src/pages/Forgot/index.tsx
Executable file
14
src/pages/Forgot/index.tsx
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
import loadable from "@loadable/component"
|
||||||
|
|
||||||
|
import { Loading, ErrorBoundary } from "../../components"
|
||||||
|
import { Props } from "./ForgotPage"
|
||||||
|
|
||||||
|
const ForgotPage = loadable(() => import("./ForgotPage"), {
|
||||||
|
fallback: <Loading />,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default (props: Props): JSX.Element => (
|
||||||
|
<ErrorBoundary>
|
||||||
|
<ForgotPage {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
)
|
9
src/pages/Forgot/styles.module.scss
Executable file
9
src/pages/Forgot/styles.module.scss
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
@import "../../theme/mixins";
|
||||||
|
|
||||||
|
.forgotPage {
|
||||||
|
@include page-wrapper-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forgotContent {
|
||||||
|
@include page-content-wrapper;
|
||||||
|
}
|
@ -9,18 +9,18 @@ import styles from "./styles.module.scss"
|
|||||||
|
|
||||||
export type Props = RouteComponentProps
|
export type Props = RouteComponentProps
|
||||||
|
|
||||||
const RegisterPage: React.FC<Props> = (): JSX.Element => {
|
const LoginPage: React.FC<Props> = (): JSX.Element => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const loginError = useSelector((state: AppState) => state.volunteerLogin.error, shallowEqual)
|
const loginError = useSelector((state: AppState) => state.volunteerLogin.error, shallowEqual)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.loginPage}>
|
<div className={styles.loginPage}>
|
||||||
<div className={styles.loginContent}>
|
<div className={styles.loginContent}>
|
||||||
<Helmet title="RegisterPage" />
|
<Helmet title="LoginPage" />
|
||||||
<LoginForm dispatch={dispatch} error={loginError || ""} />
|
<LoginForm dispatch={dispatch} error={loginError || ""} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(RegisterPage)
|
export default memo(LoginPage)
|
||||||
|
@ -5,6 +5,7 @@ import AsyncHome, { loadData as loadHomeData } from "../pages/Home"
|
|||||||
import AsyncWish, { loadData as loadWishData } from "../pages/Wish"
|
import AsyncWish, { loadData as loadWishData } from "../pages/Wish"
|
||||||
import AsyncVolunteerPage, { loadData as loadVolunteerPageData } from "../pages/VolunteerPage"
|
import AsyncVolunteerPage, { loadData as loadVolunteerPageData } from "../pages/VolunteerPage"
|
||||||
import Login from "../pages/Login"
|
import Login from "../pages/Login"
|
||||||
|
import Forgot from "../pages/Forgot"
|
||||||
import Register from "../pages/Register"
|
import Register from "../pages/Register"
|
||||||
import NotFound from "../pages/NotFound"
|
import NotFound from "../pages/NotFound"
|
||||||
|
|
||||||
@ -26,6 +27,10 @@ export default [
|
|||||||
path: "/login",
|
path: "/login",
|
||||||
component: Login,
|
component: Login,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/forgot",
|
||||||
|
component: Forgot,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/home",
|
path: "/home",
|
||||||
component: AsyncHome,
|
component: AsyncHome,
|
||||||
|
@ -120,6 +120,7 @@ export class Sheet<
|
|||||||
if (!foundElement) {
|
if (!foundElement) {
|
||||||
throw new Error(`No element found to be set in ${this.name} at id ${element.id}`)
|
throw new Error(`No element found to be set in ${this.name} at id ${element.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_.isEqual(foundElement, element)) {
|
if (!_.isEqual(foundElement, element)) {
|
||||||
Object.assign(foundElement, element)
|
Object.assign(foundElement, element)
|
||||||
await this.setList(elements)
|
await this.setList(elements)
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { Request, Response, NextFunction } from "express"
|
import { Request, Response, NextFunction } from "express"
|
||||||
import { SheetNames, ElementWithId, getSheet, Sheet } from "./accessors"
|
import { SheetNames, ElementWithId, getSheet, Sheet } from "./accessors"
|
||||||
|
|
||||||
|
export type RequestBody = Request["body"]
|
||||||
|
export type CustomSetReturn<Element> = { toDatabase: Element; toCaller: any }
|
||||||
|
|
||||||
export default class ExpressAccessors<
|
export default class ExpressAccessors<
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
ElementNoId extends object,
|
ElementNoId extends object,
|
||||||
@ -27,23 +30,27 @@ export default class ExpressAccessors<
|
|||||||
if (elements) {
|
if (elements) {
|
||||||
response.status(200).json(elements)
|
response.status(200).json(elements)
|
||||||
}
|
}
|
||||||
} catch (e: unknown) {
|
} catch (e: any) {
|
||||||
response.status(400).json(e)
|
response.status(200).json({ error: e.message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get() {
|
// custom can be async
|
||||||
|
get(custom?: (list: Element[], body: Request["body"]) => Promise<any> | any) {
|
||||||
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
|
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
|
const list = (await this.sheet.getList()) || []
|
||||||
|
let toCaller: any
|
||||||
|
if (!custom) {
|
||||||
const id = parseInt(request.query.id as string, 10) || -1
|
const id = parseInt(request.query.id as string, 10) || -1
|
||||||
const elements = await this.sheet.getList()
|
toCaller = list.find((e: Element) => e.id === id)
|
||||||
if (elements) {
|
} else {
|
||||||
const element = elements.find((e: Element) => e.id === id)
|
toCaller = await custom(list, request.body)
|
||||||
response.status(200).json(element)
|
|
||||||
}
|
}
|
||||||
} catch (e: unknown) {
|
response.status(200).json(toCaller)
|
||||||
response.status(400).json(e)
|
} catch (e: any) {
|
||||||
|
response.status(200).json({ error: e.message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,31 +62,36 @@ export default class ExpressAccessors<
|
|||||||
if (element) {
|
if (element) {
|
||||||
response.status(200).json(element)
|
response.status(200).json(element)
|
||||||
}
|
}
|
||||||
} catch (e: unknown) {
|
} catch (e: any) {
|
||||||
response.status(400).json(e)
|
response.status(200).json({ error: e.message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set() {
|
// custom can be async
|
||||||
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
|
set(
|
||||||
try {
|
custom?: (
|
||||||
await this.sheet.set(request.body)
|
list: Element[],
|
||||||
response.status(200)
|
body: RequestBody
|
||||||
} catch (e: unknown) {
|
) => Promise<CustomSetReturn<Element>> | CustomSetReturn<Element>
|
||||||
response.status(400).json(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// transformer can be an async function
|
|
||||||
customGet(
|
|
||||||
transformer: (list: Element[] | undefined, body?: Request["body"]) => Promise<any> | any
|
|
||||||
) {
|
) {
|
||||||
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
|
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const elements = await this.sheet.getList()
|
if (!custom) {
|
||||||
response.status(200).json(await transformer(elements, request.body))
|
await this.sheet.set(request.body)
|
||||||
|
response.status(200)
|
||||||
|
} else {
|
||||||
|
const list = (await this.sheet.getList()) || []
|
||||||
|
const { toDatabase, toCaller } = await custom(list, request.body)
|
||||||
|
if (toDatabase !== undefined) {
|
||||||
|
await this.sheet.set(toDatabase)
|
||||||
|
}
|
||||||
|
if (toCaller !== undefined) {
|
||||||
|
response.status(200).json(toCaller)
|
||||||
|
} else {
|
||||||
|
response.status(200)
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
response.status(200).json({ error: e.message })
|
response.status(200).json({ error: e.message })
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,6 @@ const expressAccessor = new ExpressAccessors<JavGameWithoutId, JavGame>(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export const javGameListGet = expressAccessor.listGet()
|
export const javGameListGet = expressAccessor.listGet()
|
||||||
|
|
||||||
export const javGameGet = expressAccessor.get()
|
export const javGameGet = expressAccessor.get()
|
||||||
|
|
||||||
export const javGameAdd = expressAccessor.add()
|
export const javGameAdd = expressAccessor.add()
|
||||||
|
|
||||||
export const javGameSet = expressAccessor.set()
|
export const javGameSet = expressAccessor.set()
|
||||||
|
@ -12,13 +12,10 @@ const expressAccessor = new ExpressAccessors<PreVolunteerWithoutId, PreVolunteer
|
|||||||
)
|
)
|
||||||
|
|
||||||
export const preVolunteerListGet = expressAccessor.listGet()
|
export const preVolunteerListGet = expressAccessor.listGet()
|
||||||
|
|
||||||
export const preVolunteerGet = expressAccessor.get()
|
export const preVolunteerGet = expressAccessor.get()
|
||||||
|
|
||||||
export const preVolunteerAdd = expressAccessor.add()
|
export const preVolunteerAdd = expressAccessor.add()
|
||||||
|
|
||||||
export const preVolunteerSet = expressAccessor.set()
|
export const preVolunteerSet = expressAccessor.set()
|
||||||
|
|
||||||
export const preVolunteerCountGet = expressAccessor.customGet(
|
export const preVolunteerCountGet = expressAccessor.get(
|
||||||
(list?: PreVolunteer[]) => (list && list.length) || 0
|
(list?: PreVolunteer[]) => (list && list.length) || 0
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Request } from "express"
|
import _ from "lodash"
|
||||||
import bcrypt from "bcrypt"
|
import bcrypt from "bcrypt"
|
||||||
|
import sgMail from "@sendgrid/mail"
|
||||||
|
|
||||||
import ExpressAccessors from "./expressAccessors"
|
import ExpressAccessors, { RequestBody } from "./expressAccessors"
|
||||||
import { Volunteer, VolunteerWithoutId, translationVolunteer } from "../../services/volunteers"
|
import { Volunteer, VolunteerWithoutId, translationVolunteer } from "../../services/volunteers"
|
||||||
import { canonicalEmail } from "../../utils/standardization"
|
import { canonicalEmail } from "../../utils/standardization"
|
||||||
import { getJwt } from "../secure"
|
import { getJwt } from "../secure"
|
||||||
@ -13,38 +14,98 @@ const expressAccessor = new ExpressAccessors<VolunteerWithoutId, Volunteer>(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export const volunteerListGet = expressAccessor.listGet()
|
export const volunteerListGet = expressAccessor.listGet()
|
||||||
|
|
||||||
export const volunteerGet = expressAccessor.get()
|
export const volunteerGet = expressAccessor.get()
|
||||||
|
|
||||||
export const volunteerAdd = expressAccessor.add()
|
export const volunteerAdd = expressAccessor.add()
|
||||||
|
|
||||||
export const volunteerSet = expressAccessor.set()
|
export const volunteerSet = expressAccessor.set()
|
||||||
|
|
||||||
export const volunteerLogin = expressAccessor.customGet(
|
export const volunteerLogin = expressAccessor.get(async (list: Volunteer[], body: RequestBody) => {
|
||||||
async (list: Volunteer[] | undefined, body: Request["body"]) => {
|
const volunteer = getByEmail(list, body.email)
|
||||||
if (!list) {
|
|
||||||
throw Error("Il n'y a aucun bénévole avec cet email")
|
|
||||||
}
|
|
||||||
const email = canonicalEmail(body.email || "")
|
|
||||||
const volunteer = list.find((v) => canonicalEmail(v.email) === email)
|
|
||||||
if (!volunteer) {
|
if (!volunteer) {
|
||||||
throw Error("Il n'y a aucun bénévole avec cet email")
|
throw Error("Il n'y a aucun bénévole avec cet email")
|
||||||
}
|
}
|
||||||
|
|
||||||
const password = body.password || ""
|
const password = body.password || ""
|
||||||
const passwordMatch = await bcrypt.compare(
|
const password1Match = await bcrypt.compare(
|
||||||
password,
|
password,
|
||||||
volunteer.password.replace(/^\$2y/, "$2a")
|
volunteer.password1.replace(/^\$2y/, "$2a")
|
||||||
)
|
)
|
||||||
if (!passwordMatch) {
|
if (!password1Match) {
|
||||||
|
const password2Match = await bcrypt.compare(
|
||||||
|
password,
|
||||||
|
volunteer.password2.replace(/^\$2y/, "$2a")
|
||||||
|
)
|
||||||
|
if (!password2Match) {
|
||||||
throw Error("Mauvais mot de passe pour cet email")
|
throw Error("Mauvais mot de passe pour cet email")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const jwt = await getJwt(email)
|
const jwt = await getJwt(volunteer.email)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
firstname: volunteer.firstname,
|
firstname: volunteer.firstname,
|
||||||
jwt,
|
jwt,
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const lastForgot: { [id: string]: number } = {}
|
||||||
|
export const volunteerForgot = expressAccessor.set(async (list: Volunteer[], body: RequestBody) => {
|
||||||
|
const volunteer = getByEmail(list, body.email)
|
||||||
|
if (!volunteer) {
|
||||||
|
throw Error("Il n'y a aucun bénévole avec cet email")
|
||||||
}
|
}
|
||||||
|
const newVolunteer = _.cloneDeep(volunteer)
|
||||||
|
|
||||||
|
const now = +new Date()
|
||||||
|
const timeSinceLastSent = now - lastForgot[volunteer.id]
|
||||||
|
if (timeSinceLastSent < 2 * 60 * 1000) {
|
||||||
|
throw Error(
|
||||||
|
"Un email t'a déjà été envoyé avec un nouveau mot de passe. Es-tu sûr qu'il n'est pas dans tes spams ?"
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
lastForgot[volunteer.id] = now
|
||||||
|
|
||||||
|
const password = generatePassword()
|
||||||
|
const passwordHash = await bcrypt.hash(password, 10)
|
||||||
|
newVolunteer.password2 = passwordHash
|
||||||
|
|
||||||
|
await sendForgetEmail(volunteer.email, password)
|
||||||
|
|
||||||
|
return {
|
||||||
|
toDatabase: newVolunteer,
|
||||||
|
toCaller: {
|
||||||
|
message: `Un nouveau mot de passe t'a été envoyé par email. Regarde bien dans tes spams, ils pourrait y être :/`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function getByEmail(list: Volunteer[], rawEmail: string): Volunteer | undefined {
|
||||||
|
const email = canonicalEmail(rawEmail || "")
|
||||||
|
const volunteer = list.find((v) => canonicalEmail(v.email) === email)
|
||||||
|
return volunteer
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePassword(): string {
|
||||||
|
const s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
return Array(16)
|
||||||
|
.join()
|
||||||
|
.split(",")
|
||||||
|
.map(() => s.charAt(Math.floor(Math.random() * s.length)))
|
||||||
|
.join("")
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendForgetEmail(email: string, password: string): Promise<void> {
|
||||||
|
const apiKey = process.env.SENDGRID_API_KEY || ""
|
||||||
|
if (__DEV__ || apiKey === "") {
|
||||||
|
console.error(`Fake sending forget email to ${email} with password ${password}`)
|
||||||
|
} else {
|
||||||
|
sgMail.setApiKey(apiKey)
|
||||||
|
const msg = {
|
||||||
|
to: email,
|
||||||
|
from: "contact@parisestludique.fr",
|
||||||
|
subject: "Nouveau mot de passe pour le site de Paris est Ludique",
|
||||||
|
text: `Voici le nouveau mot de passe : ${password}\nL'ancien fonctionne encore, si tu t'en rappelles.`,
|
||||||
|
html: `Voici le nouveau mot de passe : <strong>${password}</strong><br />L'ancien fonctionne encore, si tu t'en rappelles.`,
|
||||||
|
}
|
||||||
|
await sgMail.send(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,9 +8,6 @@ const expressAccessor = new ExpressAccessors<WishWithoutId, Wish>(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export const wishListGet = expressAccessor.listGet()
|
export const wishListGet = expressAccessor.listGet()
|
||||||
|
|
||||||
export const wishGet = expressAccessor.get()
|
export const wishGet = expressAccessor.get()
|
||||||
|
|
||||||
export const wishAdd = expressAccessor.add()
|
export const wishAdd = expressAccessor.add()
|
||||||
|
|
||||||
export const wishSet = expressAccessor.set()
|
export const wishSet = expressAccessor.set()
|
||||||
|
@ -19,7 +19,7 @@ import { secure } from "./secure"
|
|||||||
import { javGameListGet } from "./gsheets/javGames"
|
import { javGameListGet } from "./gsheets/javGames"
|
||||||
import { wishListGet, wishAdd } from "./gsheets/wishes"
|
import { wishListGet, wishAdd } from "./gsheets/wishes"
|
||||||
import { preVolunteerAdd, preVolunteerCountGet } from "./gsheets/preVolunteers"
|
import { preVolunteerAdd, preVolunteerCountGet } from "./gsheets/preVolunteers"
|
||||||
import { volunteerGet, volunteerSet, volunteerLogin } from "./gsheets/volunteers"
|
import { volunteerGet, volunteerSet, volunteerLogin, volunteerForgot } from "./gsheets/volunteers"
|
||||||
import config from "../config"
|
import config from "../config"
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
@ -56,6 +56,7 @@ app.post("/WishAdd", wishAdd)
|
|||||||
app.post("/PreVolunteerAdd", preVolunteerAdd)
|
app.post("/PreVolunteerAdd", preVolunteerAdd)
|
||||||
app.get("/PreVolunteerCountGet", preVolunteerCountGet)
|
app.get("/PreVolunteerCountGet", preVolunteerCountGet)
|
||||||
app.post("/VolunteerLogin", volunteerLogin)
|
app.post("/VolunteerLogin", volunteerLogin)
|
||||||
|
app.post("/VolunteerForgot", volunteerForgot)
|
||||||
|
|
||||||
// Secured APIs
|
// Secured APIs
|
||||||
app.get("/VolunteerGet", secure as RequestHandler, volunteerGet)
|
app.get("/VolunteerGet", secure as RequestHandler, volunteerGet)
|
||||||
|
@ -7,12 +7,15 @@ export type ElementWithId = unknown & { id: number }
|
|||||||
|
|
||||||
export type ElementTranslation<Element> = { [k in keyof Element]: string }
|
export type ElementTranslation<Element> = { [k in keyof Element]: string }
|
||||||
|
|
||||||
export default function getServiceAccessors<
|
export default class ServiceAccessors<
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
ElementNoId extends object,
|
ElementNoId extends object,
|
||||||
Element extends ElementNoId & ElementWithId
|
Element extends ElementNoId & ElementWithId
|
||||||
>(elementName: string): any {
|
> {
|
||||||
function get(): (id: number) => Promise<{
|
// eslint-disable-next-line no-useless-constructor
|
||||||
|
constructor(readonly elementName: string) {}
|
||||||
|
|
||||||
|
get(): (id: number) => Promise<{
|
||||||
data?: Element
|
data?: Element
|
||||||
error?: Error
|
error?: Error
|
||||||
}> {
|
}> {
|
||||||
@ -22,7 +25,7 @@ export default function getServiceAccessors<
|
|||||||
}
|
}
|
||||||
return async (id: number): Promise<ElementGetResponse> => {
|
return async (id: number): Promise<ElementGetResponse> => {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get(`${config.API_URL}/${elementName}Get`, {
|
const { data } = await axios.get(`${config.API_URL}/${this.elementName}Get`, {
|
||||||
...axiosConfig,
|
...axiosConfig,
|
||||||
params: { id },
|
params: { id },
|
||||||
})
|
})
|
||||||
@ -33,7 +36,7 @@ export default function getServiceAccessors<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function listGet(): () => Promise<{
|
listGet(): () => Promise<{
|
||||||
data?: Element[]
|
data?: Element[]
|
||||||
error?: Error
|
error?: Error
|
||||||
}> {
|
}> {
|
||||||
@ -44,7 +47,7 @@ export default function getServiceAccessors<
|
|||||||
return async (): Promise<ElementListGetResponse> => {
|
return async (): Promise<ElementListGetResponse> => {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get(
|
const { data } = await axios.get(
|
||||||
`${config.API_URL}/${elementName}ListGet`,
|
`${config.API_URL}/${this.elementName}ListGet`,
|
||||||
axiosConfig
|
axiosConfig
|
||||||
)
|
)
|
||||||
return { data }
|
return { data }
|
||||||
@ -55,7 +58,7 @@ export default function getServiceAccessors<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
function add(): (volunteerWithoutId: ElementNoId) => Promise<{
|
add(): (volunteerWithoutId: ElementNoId) => Promise<{
|
||||||
data?: Element
|
data?: Element
|
||||||
error?: Error
|
error?: Error
|
||||||
}> {
|
}> {
|
||||||
@ -66,7 +69,7 @@ export default function getServiceAccessors<
|
|||||||
return async (volunteerWithoutId: ElementNoId): Promise<ElementGetResponse> => {
|
return async (volunteerWithoutId: ElementNoId): Promise<ElementGetResponse> => {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post(
|
const { data } = await axios.post(
|
||||||
`${config.API_URL}/${elementName}Add`,
|
`${config.API_URL}/${this.elementName}Add`,
|
||||||
volunteerWithoutId,
|
volunteerWithoutId,
|
||||||
axiosConfig
|
axiosConfig
|
||||||
)
|
)
|
||||||
@ -77,7 +80,7 @@ export default function getServiceAccessors<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function set(): (volunteer: Element) => Promise<{
|
set(): (volunteer: Element) => Promise<{
|
||||||
data?: Element
|
data?: Element
|
||||||
error?: Error
|
error?: Error
|
||||||
}> {
|
}> {
|
||||||
@ -88,7 +91,7 @@ export default function getServiceAccessors<
|
|||||||
return async (volunteer: Element): Promise<ElementGetResponse> => {
|
return async (volunteer: Element): Promise<ElementGetResponse> => {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post(
|
const { data } = await axios.post(
|
||||||
`${config.API_URL}/${elementName}Set`,
|
`${config.API_URL}/${this.elementName}Set`,
|
||||||
volunteer,
|
volunteer,
|
||||||
axiosConfig
|
axiosConfig
|
||||||
)
|
)
|
||||||
@ -99,7 +102,7 @@ export default function getServiceAccessors<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function countGet(): () => Promise<{
|
countGet(): () => Promise<{
|
||||||
data?: number
|
data?: number
|
||||||
error?: Error
|
error?: Error
|
||||||
}> {
|
}> {
|
||||||
@ -110,7 +113,7 @@ export default function getServiceAccessors<
|
|||||||
return async (): Promise<ElementCountGetResponse> => {
|
return async (): Promise<ElementCountGetResponse> => {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get(
|
const { data } = await axios.get(
|
||||||
`${config.API_URL}/${elementName}CountGet`,
|
`${config.API_URL}/${this.elementName}CountGet`,
|
||||||
axiosConfig
|
axiosConfig
|
||||||
)
|
)
|
||||||
return { data }
|
return { data }
|
||||||
@ -120,18 +123,18 @@ export default function getServiceAccessors<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function customPost(apiName: string): (params: any) => Promise<{
|
customPost(apiName: string): (params: any) => Promise<{
|
||||||
data?: Element
|
data?: any
|
||||||
error?: Error
|
error?: Error
|
||||||
}> {
|
}> {
|
||||||
interface ElementGetResponse {
|
interface ElementGetResponse {
|
||||||
data?: Element
|
data?: any
|
||||||
error?: Error
|
error?: Error
|
||||||
}
|
}
|
||||||
return async (params: any): Promise<ElementGetResponse> => {
|
return async (params: any): Promise<ElementGetResponse> => {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post(
|
const { data } = await axios.post(
|
||||||
`${config.API_URL}/${elementName}${apiName}`,
|
`${config.API_URL}/${this.elementName}${apiName}`,
|
||||||
params,
|
params,
|
||||||
axiosConfig
|
axiosConfig
|
||||||
)
|
)
|
||||||
@ -144,6 +147,4 @@ export default function getServiceAccessors<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { listGet, get, set, add, countGet, customPost }
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import getServiceAccessors from "./accessors"
|
import ServiceAccessors from "./accessors"
|
||||||
|
|
||||||
export class JavGame {
|
export class JavGame {
|
||||||
id = 0
|
id = 0
|
||||||
@ -54,9 +54,9 @@ const elementName = "JavGame"
|
|||||||
|
|
||||||
export type JavGameWithoutId = Omit<JavGame, "id">
|
export type JavGameWithoutId = Omit<JavGame, "id">
|
||||||
|
|
||||||
const { listGet, get, set, add } = getServiceAccessors<JavGameWithoutId, JavGame>(elementName)
|
const serviceAccessors = new ServiceAccessors<JavGameWithoutId, JavGame>(elementName)
|
||||||
|
|
||||||
export const javGameListGet = listGet()
|
export const javGameListGet = serviceAccessors.listGet()
|
||||||
export const javGameGet = get()
|
export const javGameGet = serviceAccessors.get()
|
||||||
export const javGameAdd = add()
|
export const javGameAdd = serviceAccessors.add()
|
||||||
export const javGameSet = set()
|
export const javGameSet = serviceAccessors.set()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import getServiceAccessors from "./accessors"
|
import ServiceAccessors from "./accessors"
|
||||||
|
|
||||||
export class PreVolunteer {
|
export class PreVolunteer {
|
||||||
id = 0
|
id = 0
|
||||||
@ -34,13 +34,10 @@ export const passwordMinLength = 4
|
|||||||
|
|
||||||
export type PreVolunteerWithoutId = Omit<PreVolunteer, "id">
|
export type PreVolunteerWithoutId = Omit<PreVolunteer, "id">
|
||||||
|
|
||||||
const { listGet, get, set, add, countGet } = getServiceAccessors<
|
const serviceAccessors = new ServiceAccessors<PreVolunteerWithoutId, PreVolunteer>(elementName)
|
||||||
PreVolunteerWithoutId,
|
|
||||||
PreVolunteer
|
|
||||||
>(elementName)
|
|
||||||
|
|
||||||
export const preVolunteerListGet = listGet()
|
export const preVolunteerListGet = serviceAccessors.listGet()
|
||||||
export const preVolunteerGet = get()
|
export const preVolunteerGet = serviceAccessors.get()
|
||||||
export const preVolunteerAdd = add()
|
export const preVolunteerAdd = serviceAccessors.add()
|
||||||
export const preVolunteerSet = set()
|
export const preVolunteerSet = serviceAccessors.set()
|
||||||
export const preVolunteerCountGet = countGet()
|
export const preVolunteerCountGet = serviceAccessors.countGet()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import getServiceAccessors from "./accessors"
|
import ServiceAccessors from "./accessors"
|
||||||
|
|
||||||
export class Volunteer {
|
export class Volunteer {
|
||||||
id = 0
|
id = 0
|
||||||
@ -21,11 +21,11 @@ export class Volunteer {
|
|||||||
|
|
||||||
active = 0
|
active = 0
|
||||||
|
|
||||||
comment = ""
|
created = new Date()
|
||||||
|
|
||||||
timestamp = new Date()
|
password1 = ""
|
||||||
|
|
||||||
password = ""
|
password2 = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
export const translationVolunteer: { [k in keyof Volunteer]: string } = {
|
export const translationVolunteer: { [k in keyof Volunteer]: string } = {
|
||||||
@ -39,9 +39,9 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
|
|||||||
adult: "majeur",
|
adult: "majeur",
|
||||||
privileges: "privilege",
|
privileges: "privilege",
|
||||||
active: "actif",
|
active: "actif",
|
||||||
comment: "commentaire",
|
created: "creation",
|
||||||
timestamp: "horodatage",
|
password1: "passe1",
|
||||||
password: "passe",
|
password2: "passe2",
|
||||||
}
|
}
|
||||||
|
|
||||||
const elementName = "Volunteer"
|
const elementName = "Volunteer"
|
||||||
@ -52,16 +52,20 @@ export const passwordMinLength = 4
|
|||||||
|
|
||||||
export type VolunteerWithoutId = Omit<Volunteer, "id">
|
export type VolunteerWithoutId = Omit<Volunteer, "id">
|
||||||
|
|
||||||
const accessors = getServiceAccessors<VolunteerWithoutId, Volunteer>(elementName)
|
const serviceAccessors = new ServiceAccessors<VolunteerWithoutId, Volunteer>(elementName)
|
||||||
const { listGet, get, set, add } = accessors
|
|
||||||
|
|
||||||
export const volunteerListGet = listGet()
|
export const volunteerListGet = serviceAccessors.listGet()
|
||||||
export const volunteerGet = get()
|
export const volunteerGet = serviceAccessors.get()
|
||||||
export const volunteerAdd = add()
|
export const volunteerAdd = serviceAccessors.add()
|
||||||
export const volunteerSet = set()
|
export const volunteerSet = serviceAccessors.set()
|
||||||
|
|
||||||
export interface VolunteerLogin {
|
export interface VolunteerLogin {
|
||||||
firstname: string
|
firstname: string
|
||||||
jwt: string
|
jwt: string
|
||||||
}
|
}
|
||||||
export const volunteerLogin = accessors.customPost("Login")
|
export const volunteerLogin = serviceAccessors.customPost("Login")
|
||||||
|
|
||||||
|
export interface VolunteerForgot {
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
export const volunteerForgot = serviceAccessors.customPost("Forgot")
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import getServiceAccessors from "./accessors"
|
import ServiceAccessors from "./accessors"
|
||||||
|
|
||||||
export class Wish {
|
export class Wish {
|
||||||
id = 0
|
id = 0
|
||||||
@ -27,9 +27,9 @@ const elementName = "Wish"
|
|||||||
|
|
||||||
export type WishWithoutId = Omit<Wish, "id">
|
export type WishWithoutId = Omit<Wish, "id">
|
||||||
|
|
||||||
const { listGet, get, set, add } = getServiceAccessors<WishWithoutId, Wish>(elementName)
|
const serviceAccessors = new ServiceAccessors<WishWithoutId, Wish>(elementName)
|
||||||
|
|
||||||
export const wishListGet = listGet()
|
export const wishListGet = serviceAccessors.listGet()
|
||||||
export const wishGet = get()
|
export const wishGet = serviceAccessors.get()
|
||||||
export const wishAdd = add()
|
export const wishAdd = serviceAccessors.add()
|
||||||
export const wishSet = set()
|
export const wishSet = serviceAccessors.set()
|
||||||
|
@ -23,9 +23,9 @@ const mockData: Volunteer = {
|
|||||||
adult: 1,
|
adult: 1,
|
||||||
privileges: 0,
|
privileges: 0,
|
||||||
active: 0,
|
active: 0,
|
||||||
comment: "",
|
created: new Date(0),
|
||||||
timestamp: new Date(0),
|
password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
|
||||||
password: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
|
password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
|
||||||
}
|
}
|
||||||
const { id } = mockData
|
const { id } = mockData
|
||||||
const mockError = "Oops! Something went wrong."
|
const mockError = "Oops! Something went wrong."
|
||||||
|
@ -25,9 +25,9 @@ const mockData: Volunteer[] = [
|
|||||||
adult: 1,
|
adult: 1,
|
||||||
privileges: 0,
|
privileges: 0,
|
||||||
active: 0,
|
active: 0,
|
||||||
comment: "",
|
created: new Date(0),
|
||||||
timestamp: new Date(0),
|
password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
|
||||||
password: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
|
password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
const mockError = "Oops! Something went wrong."
|
const mockError = "Oops! Something went wrong."
|
||||||
|
@ -9,6 +9,7 @@ import volunteerAdd from "./volunteerAdd"
|
|||||||
import volunteerList from "./volunteerList"
|
import volunteerList from "./volunteerList"
|
||||||
import volunteerSet from "./volunteerSet"
|
import volunteerSet from "./volunteerSet"
|
||||||
import volunteerLogin from "./volunteerLogin"
|
import volunteerLogin from "./volunteerLogin"
|
||||||
|
import volunteerForgot from "./volunteerForgot"
|
||||||
import preVolunteerAdd from "./preVolunteerAdd"
|
import preVolunteerAdd from "./preVolunteerAdd"
|
||||||
import preVolunteerCount from "./preVolunteerCount"
|
import preVolunteerCount from "./preVolunteerCount"
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ export default (history: History) => ({
|
|||||||
volunteerList,
|
volunteerList,
|
||||||
volunteerSet,
|
volunteerSet,
|
||||||
volunteerLogin,
|
volunteerLogin,
|
||||||
|
volunteerForgot,
|
||||||
preVolunteerAdd,
|
preVolunteerAdd,
|
||||||
preVolunteerCount,
|
preVolunteerCount,
|
||||||
router: connectRouter(history) as any,
|
router: connectRouter(history) as any,
|
||||||
|
@ -51,14 +51,10 @@ export function elementFetch<Element>(
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
dispatch(getFailure(error.message))
|
dispatch(getFailure(error.message))
|
||||||
if (errorMessage) {
|
errorMessage?.(error)
|
||||||
errorMessage(error)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
dispatch(getSuccess(data as Element))
|
dispatch(getSuccess(data as Element))
|
||||||
if (successMessage) {
|
successMessage?.(data as Element)
|
||||||
successMessage(data as Element)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,12 +67,8 @@ export function elementAddFetch<Element>(
|
|||||||
getRequesting: ActionCreatorWithoutPayload<string>,
|
getRequesting: ActionCreatorWithoutPayload<string>,
|
||||||
getSuccess: ActionCreatorWithPayload<Element, string>,
|
getSuccess: ActionCreatorWithPayload<Element, string>,
|
||||||
getFailure: ActionCreatorWithPayload<string, string>,
|
getFailure: ActionCreatorWithPayload<string, string>,
|
||||||
errorMessage: (error: Error) => void = (_error) => {
|
errorMessage?: (error: Error) => void,
|
||||||
/* Meant to be empty */
|
successMessage?: () => void
|
||||||
},
|
|
||||||
successMessage: () => void = () => {
|
|
||||||
/* Meant to be empty */
|
|
||||||
}
|
|
||||||
): (volunteerWithoutId: Omit<Element, "id">) => AppThunk {
|
): (volunteerWithoutId: Omit<Element, "id">) => AppThunk {
|
||||||
return (volunteerWithoutId: Omit<Element, "id">): AppThunk =>
|
return (volunteerWithoutId: Omit<Element, "id">): AppThunk =>
|
||||||
async (dispatch) => {
|
async (dispatch) => {
|
||||||
@ -86,10 +78,10 @@ export function elementAddFetch<Element>(
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
dispatch(getFailure(error.message))
|
dispatch(getFailure(error.message))
|
||||||
errorMessage(error)
|
errorMessage?.(error)
|
||||||
} else {
|
} else {
|
||||||
dispatch(getSuccess(data as Element))
|
dispatch(getSuccess(data as Element))
|
||||||
successMessage()
|
successMessage?.()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,12 +94,8 @@ export function elementListFetch<Element>(
|
|||||||
getRequesting: ActionCreatorWithoutPayload<string>,
|
getRequesting: ActionCreatorWithoutPayload<string>,
|
||||||
getSuccess: ActionCreatorWithPayload<Element[], string>,
|
getSuccess: ActionCreatorWithPayload<Element[], string>,
|
||||||
getFailure: ActionCreatorWithPayload<string, string>,
|
getFailure: ActionCreatorWithPayload<string, string>,
|
||||||
errorMessage: (error: Error) => void = (_error) => {
|
errorMessage?: (error: Error) => void,
|
||||||
/* Meant to be empty */
|
successMessage?: () => void
|
||||||
},
|
|
||||||
successMessage: () => void = () => {
|
|
||||||
/* Meant to be empty */
|
|
||||||
}
|
|
||||||
): () => AppThunk {
|
): () => AppThunk {
|
||||||
return (): AppThunk => async (dispatch) => {
|
return (): AppThunk => async (dispatch) => {
|
||||||
dispatch(getRequesting())
|
dispatch(getRequesting())
|
||||||
@ -116,10 +104,10 @@ export function elementListFetch<Element>(
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
dispatch(getFailure(error.message))
|
dispatch(getFailure(error.message))
|
||||||
errorMessage(error)
|
errorMessage?.(error)
|
||||||
} else {
|
} else {
|
||||||
dispatch(getSuccess(data as Element[]))
|
dispatch(getSuccess(data as Element[]))
|
||||||
successMessage()
|
successMessage?.()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,12 +120,8 @@ export function elementSet<Element>(
|
|||||||
getRequesting: ActionCreatorWithoutPayload<string>,
|
getRequesting: ActionCreatorWithoutPayload<string>,
|
||||||
getSuccess: ActionCreatorWithPayload<Element, string>,
|
getSuccess: ActionCreatorWithPayload<Element, string>,
|
||||||
getFailure: ActionCreatorWithPayload<string, string>,
|
getFailure: ActionCreatorWithPayload<string, string>,
|
||||||
errorMessage: (error: Error) => void = (_error) => {
|
errorMessage?: (error: Error) => void,
|
||||||
/* Meant to be empty */
|
successMessage?: () => void
|
||||||
},
|
|
||||||
successMessage: () => void = () => {
|
|
||||||
/* Meant to be empty */
|
|
||||||
}
|
|
||||||
): (element: Element) => AppThunk {
|
): (element: Element) => AppThunk {
|
||||||
return (element: Element): AppThunk =>
|
return (element: Element): AppThunk =>
|
||||||
async (dispatch) => {
|
async (dispatch) => {
|
||||||
@ -147,10 +131,10 @@ export function elementSet<Element>(
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
dispatch(getFailure(error.message))
|
dispatch(getFailure(error.message))
|
||||||
errorMessage(error)
|
errorMessage?.(error)
|
||||||
} else {
|
} else {
|
||||||
dispatch(getSuccess(data as Element))
|
dispatch(getSuccess(data as Element))
|
||||||
successMessage()
|
successMessage?.()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,12 +147,8 @@ export function elementValueFetch<Element>(
|
|||||||
getRequesting: ActionCreatorWithoutPayload<string>,
|
getRequesting: ActionCreatorWithoutPayload<string>,
|
||||||
getSuccess: ActionCreatorWithPayload<Element, string>,
|
getSuccess: ActionCreatorWithPayload<Element, string>,
|
||||||
getFailure: ActionCreatorWithPayload<string, string>,
|
getFailure: ActionCreatorWithPayload<string, string>,
|
||||||
errorMessage: (error: Error) => void = (_error) => {
|
errorMessage?: (error: Error) => void,
|
||||||
/* Meant to be empty */
|
successMessage?: () => void
|
||||||
},
|
|
||||||
successMessage: () => void = () => {
|
|
||||||
/* Meant to be empty */
|
|
||||||
}
|
|
||||||
): () => AppThunk {
|
): () => AppThunk {
|
||||||
return (): AppThunk => async (dispatch) => {
|
return (): AppThunk => async (dispatch) => {
|
||||||
dispatch(getRequesting())
|
dispatch(getRequesting())
|
||||||
@ -177,10 +157,10 @@ export function elementValueFetch<Element>(
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
dispatch(getFailure(error.message))
|
dispatch(getFailure(error.message))
|
||||||
errorMessage(error)
|
errorMessage?.(error)
|
||||||
} else {
|
} else {
|
||||||
dispatch(getSuccess(data as Element))
|
dispatch(getSuccess(data as Element))
|
||||||
successMessage()
|
successMessage?.()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
src/store/volunteerForgot.ts
Normal file
38
src/store/volunteerForgot.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { PayloadAction, createSlice } from "@reduxjs/toolkit"
|
||||||
|
|
||||||
|
import { StateRequest, elementFetch } from "./utils"
|
||||||
|
import { VolunteerForgot, volunteerForgot } from "../services/volunteers"
|
||||||
|
|
||||||
|
type StateVolunteer = { entity?: VolunteerForgot } & StateRequest
|
||||||
|
|
||||||
|
export const initialState: StateVolunteer = {
|
||||||
|
readyStatus: "idle",
|
||||||
|
}
|
||||||
|
|
||||||
|
const volunteerForgotSlice = createSlice({
|
||||||
|
name: "volunteerForgot",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
getRequesting: (_) => ({
|
||||||
|
readyStatus: "request",
|
||||||
|
}),
|
||||||
|
getSuccess: (_, { payload }: PayloadAction<VolunteerForgot>) => ({
|
||||||
|
readyStatus: "success",
|
||||||
|
entity: payload,
|
||||||
|
}),
|
||||||
|
getFailure: (_, { payload }: PayloadAction<string>) => ({
|
||||||
|
readyStatus: "failure",
|
||||||
|
error: payload,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default volunteerForgotSlice.reducer
|
||||||
|
export const { getRequesting, getSuccess, getFailure } = volunteerForgotSlice.actions
|
||||||
|
|
||||||
|
export const fetchVolunteerForgot = elementFetch(
|
||||||
|
volunteerForgot,
|
||||||
|
getRequesting,
|
||||||
|
getSuccess,
|
||||||
|
getFailure
|
||||||
|
)
|
@ -11,7 +11,7 @@ export const initialState: StateVolunteer = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const volunteerLoginSlice = createSlice({
|
const volunteerLoginSlice = createSlice({
|
||||||
name: "volunteer",
|
name: "volunteerLogin",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
getRequesting: (_) => ({
|
getRequesting: (_) => ({
|
||||||
|
2
src/types/index.d.ts
vendored
2
src/types/index.d.ts
vendored
@ -3,6 +3,7 @@ declare const __SERVER__: boolean
|
|||||||
declare const __DEV__: boolean
|
declare const __DEV__: boolean
|
||||||
declare const __LOCAL__: boolean
|
declare const __LOCAL__: boolean
|
||||||
declare const __TEST__: boolean
|
declare const __TEST__: boolean
|
||||||
|
declare const __SENDGRID_API_KEY__: string
|
||||||
|
|
||||||
declare module "*.svg"
|
declare module "*.svg"
|
||||||
declare module "*.gif"
|
declare module "*.gif"
|
||||||
@ -20,6 +21,7 @@ declare namespace NodeJS {
|
|||||||
__DEV__: boolean
|
__DEV__: boolean
|
||||||
__LOCAL__: boolean
|
__LOCAL__: boolean
|
||||||
__TEST__: boolean
|
__TEST__: boolean
|
||||||
|
__SENDGRID_API_KEY__: string
|
||||||
$RefreshReg$: () => void
|
$RefreshReg$: () => void
|
||||||
$RefreshSig$$: () => void
|
$RefreshSig$$: () => void
|
||||||
}
|
}
|
||||||
|
23
yarn.lock
23
yarn.lock
@ -1334,6 +1334,29 @@
|
|||||||
redux-thunk "^2.3.0"
|
redux-thunk "^2.3.0"
|
||||||
reselect "^4.0.0"
|
reselect "^4.0.0"
|
||||||
|
|
||||||
|
"@sendgrid/client@^7.6.0":
|
||||||
|
version "7.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sendgrid/client/-/client-7.6.0.tgz#f90cb8759c96e1d90224f29ad98f8fdc2be287f3"
|
||||||
|
integrity sha512-cpBVZKLlMTO+vpE18krTixubYmZa98oTbLkqBDuTiA3zRkW+urrxg7pDR24TkI35Mid0Zru8jDHwnOiqrXu0TA==
|
||||||
|
dependencies:
|
||||||
|
"@sendgrid/helpers" "^7.6.0"
|
||||||
|
axios "^0.21.4"
|
||||||
|
|
||||||
|
"@sendgrid/helpers@^7.6.0":
|
||||||
|
version "7.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sendgrid/helpers/-/helpers-7.6.0.tgz#b381bfab391bcd66c771811b22bb6bb2d5c1dfc6"
|
||||||
|
integrity sha512-0uWD+HSXLl4Z/X3cN+UMQC20RE7xwAACgppnfjDyvKG0KvJcUgDGz7HDdQkiMUdcVWfmyk6zKSg7XKfKzBjTwA==
|
||||||
|
dependencies:
|
||||||
|
deepmerge "^4.2.2"
|
||||||
|
|
||||||
|
"@sendgrid/mail@^7.6.0":
|
||||||
|
version "7.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sendgrid/mail/-/mail-7.6.0.tgz#e74ee30110527feab5d3b83d68af0cd94537f6d2"
|
||||||
|
integrity sha512-0KdaSZzflJD/vUAZjB3ALBIuaVGoLq22hrb2fvQXZHRepU/yhRNlEOqrr05MfKBnKskzq1blnD1J0fHxiwaolw==
|
||||||
|
dependencies:
|
||||||
|
"@sendgrid/client" "^7.6.0"
|
||||||
|
"@sendgrid/helpers" "^7.6.0"
|
||||||
|
|
||||||
"@sindresorhus/is@^0.14.0":
|
"@sindresorhus/is@^0.14.0":
|
||||||
version "0.14.0"
|
version "0.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
|
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user