diff --git a/.eslintrc.js b/.eslintrc.js
index 0184e79..39e1de8 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -57,5 +57,6 @@ module.exports = {
__DEV__: true,
__LOCAL__: false,
__TEST__: false,
+ __SENDGRID_API_KEY__: false,
},
}
diff --git a/jest/config.js b/jest/config.js
index 3ac0720..92f5a54 100644
--- a/jest/config.js
+++ b/jest/config.js
@@ -23,6 +23,7 @@ module.exports = {
__SERVER__: false,
__LOCAL__: false,
__TEST__: true,
+ __SENDGRID_API_KEY__: "",
localStorage: { getItem: () => null, setItem: () => null, removeItem: () => null },
},
maxConcurrency: 50,
diff --git a/package.json b/package.json
index 89001b6..869c895 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,7 @@
"@loadable/component": "^5.15.0",
"@loadable/server": "^5.15.0",
"@reduxjs/toolkit": "^1.6.0",
+ "@sendgrid/mail": "^7.6.0",
"@types/lodash": "^4.14.177",
"autoprefixer": "^10.2.6",
"axios": "^0.21.1",
diff --git a/src/components/ForgotForm/ForgotForm.tsx b/src/components/ForgotForm/ForgotForm.tsx
new file mode 100644
index 0000000..6c5d3ef
--- /dev/null
+++ b/src/components/ForgotForm/ForgotForm.tsx
@@ -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 (
+
+ )
+}
+
+export default memo(ForgotForm)
diff --git a/src/components/ForgotForm/styles.module.scss b/src/components/ForgotForm/styles.module.scss
new file mode 100755
index 0000000..20494de
--- /dev/null
+++ b/src/components/ForgotForm/styles.module.scss
@@ -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;
+}
diff --git a/src/components/LoginForm/LoginForm.tsx b/src/components/LoginForm/LoginForm.tsx
index 46d6b0a..0b21ea5 100644
--- a/src/components/LoginForm/LoginForm.tsx
+++ b/src/components/LoginForm/LoginForm.tsx
@@ -1,7 +1,8 @@
import React, { memo, useCallback } from "react"
-import styles from "./styles.module.scss"
+import { Link } from "react-router-dom"
import { AppDispatch } from "../../store"
import { fetchVolunteerLogin } from "../../store/volunteerLogin"
+import styles from "./styles.module.scss"
interface Props {
dispatch: AppDispatch
@@ -41,6 +42,9 @@ const LoginForm = ({ dispatch, error }: Props): JSX.Element => {
{error}
+
+ Demander un nouveau mot de passe
+
)
}
diff --git a/src/components/LoginForm/styles.module.scss b/src/components/LoginForm/styles.module.scss
index ffcaf11..a145737 100755
--- a/src/components/LoginForm/styles.module.scss
+++ b/src/components/LoginForm/styles.module.scss
@@ -26,5 +26,12 @@
}
.error {
+ margin-top: 10px;
color: rgb(255, 0, 0);
+ text-align: center;
+}
+
+.link {
+ margin-top: 20px;
+ text-align: center;
}
diff --git a/src/components/VolunteerInfo/__tests__/VolunteerInfo.tsx b/src/components/VolunteerInfo/__tests__/VolunteerInfo.tsx
index 55db3e8..787d2dc 100755
--- a/src/components/VolunteerInfo/__tests__/VolunteerInfo.tsx
+++ b/src/components/VolunteerInfo/__tests__/VolunteerInfo.tsx
@@ -22,9 +22,9 @@ describe("", () => {
adult: 1,
privileges: 0,
active: 0,
- comment: "",
- timestamp: new Date(0),
- password: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
+ created: new Date(0),
+ password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
+ password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
}}
/>
diff --git a/src/components/VolunteerList/__tests__/VolunteerList.tsx b/src/components/VolunteerList/__tests__/VolunteerList.tsx
index a7c50da..4f05e07 100755
--- a/src/components/VolunteerList/__tests__/VolunteerList.tsx
+++ b/src/components/VolunteerList/__tests__/VolunteerList.tsx
@@ -23,9 +23,10 @@ describe("", () => {
adult: 1,
privileges: 0,
active: 0,
- comment: "",
- timestamp: new Date(0),
- password:
+ created: new Date(0),
+ password1:
+ "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
+ password2:
"$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
},
]}
diff --git a/src/components/VolunteerSet/__tests__/VolunteerSet.tsx b/src/components/VolunteerSet/__tests__/VolunteerSet.tsx
index 3962b14..8d107bc 100644
--- a/src/components/VolunteerSet/__tests__/VolunteerSet.tsx
+++ b/src/components/VolunteerSet/__tests__/VolunteerSet.tsx
@@ -24,9 +24,9 @@ describe("", () => {
adult: 1,
privileges: 0,
active: 0,
- comment: "",
- timestamp: new Date(0),
- password: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
+ created: new Date(0),
+ password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
+ password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
}}
/>
diff --git a/src/pages/Forgot/ForgotPage.tsx b/src/pages/Forgot/ForgotPage.tsx
new file mode 100644
index 0000000..d1432c0
--- /dev/null
+++ b/src/pages/Forgot/ForgotPage.tsx
@@ -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 = (): 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 (
+
+ )
+}
+
+export default memo(ForgotPage)
diff --git a/src/pages/Forgot/index.tsx b/src/pages/Forgot/index.tsx
new file mode 100755
index 0000000..31205a9
--- /dev/null
+++ b/src/pages/Forgot/index.tsx
@@ -0,0 +1,14 @@
+import loadable from "@loadable/component"
+
+import { Loading, ErrorBoundary } from "../../components"
+import { Props } from "./ForgotPage"
+
+const ForgotPage = loadable(() => import("./ForgotPage"), {
+ fallback: ,
+})
+
+export default (props: Props): JSX.Element => (
+
+
+
+)
diff --git a/src/pages/Forgot/styles.module.scss b/src/pages/Forgot/styles.module.scss
new file mode 100755
index 0000000..1583b45
--- /dev/null
+++ b/src/pages/Forgot/styles.module.scss
@@ -0,0 +1,9 @@
+@import "../../theme/mixins";
+
+.forgotPage {
+ @include page-wrapper-center;
+}
+
+.forgotContent {
+ @include page-content-wrapper;
+}
diff --git a/src/pages/Login/LoginPage.tsx b/src/pages/Login/LoginPage.tsx
index 46b15c0..316bb0b 100644
--- a/src/pages/Login/LoginPage.tsx
+++ b/src/pages/Login/LoginPage.tsx
@@ -9,18 +9,18 @@ import styles from "./styles.module.scss"
export type Props = RouteComponentProps
-const RegisterPage: React.FC = (): JSX.Element => {
+const LoginPage: React.FC = (): JSX.Element => {
const dispatch = useDispatch()
const loginError = useSelector((state: AppState) => state.volunteerLogin.error, shallowEqual)
return (
)
}
-export default memo(RegisterPage)
+export default memo(LoginPage)
diff --git a/src/routes/index.ts b/src/routes/index.ts
index 10c9f7b..2cd6af8 100755
--- a/src/routes/index.ts
+++ b/src/routes/index.ts
@@ -5,6 +5,7 @@ import AsyncHome, { loadData as loadHomeData } from "../pages/Home"
import AsyncWish, { loadData as loadWishData } from "../pages/Wish"
import AsyncVolunteerPage, { loadData as loadVolunteerPageData } from "../pages/VolunteerPage"
import Login from "../pages/Login"
+import Forgot from "../pages/Forgot"
import Register from "../pages/Register"
import NotFound from "../pages/NotFound"
@@ -26,6 +27,10 @@ export default [
path: "/login",
component: Login,
},
+ {
+ path: "/forgot",
+ component: Forgot,
+ },
{
path: "/home",
component: AsyncHome,
diff --git a/src/server/gsheets/accessors.ts b/src/server/gsheets/accessors.ts
index 8575e3b..b3bc02c 100644
--- a/src/server/gsheets/accessors.ts
+++ b/src/server/gsheets/accessors.ts
@@ -120,6 +120,7 @@ export class Sheet<
if (!foundElement) {
throw new Error(`No element found to be set in ${this.name} at id ${element.id}`)
}
+
if (!_.isEqual(foundElement, element)) {
Object.assign(foundElement, element)
await this.setList(elements)
diff --git a/src/server/gsheets/expressAccessors.ts b/src/server/gsheets/expressAccessors.ts
index 7a64d57..8d53844 100644
--- a/src/server/gsheets/expressAccessors.ts
+++ b/src/server/gsheets/expressAccessors.ts
@@ -1,6 +1,9 @@
import { Request, Response, NextFunction } from "express"
import { SheetNames, ElementWithId, getSheet, Sheet } from "./accessors"
+export type RequestBody = Request["body"]
+export type CustomSetReturn = { toDatabase: Element; toCaller: any }
+
export default class ExpressAccessors<
// eslint-disable-next-line @typescript-eslint/ban-types
ElementNoId extends object,
@@ -27,23 +30,27 @@ export default class ExpressAccessors<
if (elements) {
response.status(200).json(elements)
}
- } catch (e: unknown) {
- response.status(400).json(e)
+ } catch (e: any) {
+ response.status(200).json({ error: e.message })
}
}
}
- get() {
+ // custom can be async
+ get(custom?: (list: Element[], body: Request["body"]) => Promise | any) {
return async (request: Request, response: Response, _next: NextFunction): Promise => {
try {
- const id = parseInt(request.query.id as string, 10) || -1
- const elements = await this.sheet.getList()
- if (elements) {
- const element = elements.find((e: Element) => e.id === id)
- response.status(200).json(element)
+ const list = (await this.sheet.getList()) || []
+ let toCaller: any
+ if (!custom) {
+ const id = parseInt(request.query.id as string, 10) || -1
+ toCaller = list.find((e: Element) => e.id === id)
+ } else {
+ toCaller = await custom(list, request.body)
}
- } catch (e: unknown) {
- response.status(400).json(e)
+ response.status(200).json(toCaller)
+ } catch (e: any) {
+ response.status(200).json({ error: e.message })
}
}
}
@@ -55,31 +62,36 @@ export default class ExpressAccessors<
if (element) {
response.status(200).json(element)
}
- } catch (e: unknown) {
- response.status(400).json(e)
+ } catch (e: any) {
+ response.status(200).json({ error: e.message })
}
}
}
- set() {
- return async (request: Request, response: Response, _next: NextFunction): Promise => {
- try {
- await this.sheet.set(request.body)
- response.status(200)
- } catch (e: unknown) {
- response.status(400).json(e)
- }
- }
- }
-
- // transformer can be an async function
- customGet(
- transformer: (list: Element[] | undefined, body?: Request["body"]) => Promise | any
+ // custom can be async
+ set(
+ custom?: (
+ list: Element[],
+ body: RequestBody
+ ) => Promise> | CustomSetReturn
) {
return async (request: Request, response: Response, _next: NextFunction): Promise => {
try {
- const elements = await this.sheet.getList()
- response.status(200).json(await transformer(elements, request.body))
+ if (!custom) {
+ 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) {
response.status(200).json({ error: e.message })
}
diff --git a/src/server/gsheets/javGames.ts b/src/server/gsheets/javGames.ts
index fc02994..9c2f8b9 100644
--- a/src/server/gsheets/javGames.ts
+++ b/src/server/gsheets/javGames.ts
@@ -8,9 +8,6 @@ const expressAccessor = new ExpressAccessors(
)
export const javGameListGet = expressAccessor.listGet()
-
export const javGameGet = expressAccessor.get()
-
export const javGameAdd = expressAccessor.add()
-
export const javGameSet = expressAccessor.set()
diff --git a/src/server/gsheets/preVolunteers.ts b/src/server/gsheets/preVolunteers.ts
index 036e7af..7bdf49b 100644
--- a/src/server/gsheets/preVolunteers.ts
+++ b/src/server/gsheets/preVolunteers.ts
@@ -12,13 +12,10 @@ const expressAccessor = new ExpressAccessors (list && list.length) || 0
)
diff --git a/src/server/gsheets/volunteers.ts b/src/server/gsheets/volunteers.ts
index 3b567a4..83c45e7 100644
--- a/src/server/gsheets/volunteers.ts
+++ b/src/server/gsheets/volunteers.ts
@@ -1,7 +1,8 @@
-import { Request } from "express"
+import _ from "lodash"
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 { canonicalEmail } from "../../utils/standardization"
import { getJwt } from "../secure"
@@ -13,38 +14,98 @@ const expressAccessor = new ExpressAccessors(
)
export const volunteerListGet = expressAccessor.listGet()
-
export const volunteerGet = expressAccessor.get()
-
export const volunteerAdd = expressAccessor.add()
-
export const volunteerSet = expressAccessor.set()
-export const volunteerLogin = expressAccessor.customGet(
- async (list: Volunteer[] | undefined, body: Request["body"]) => {
- 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) {
- throw Error("Il n'y a aucun bénévole avec cet email")
- }
+export const volunteerLogin = expressAccessor.get(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 password = body.password || ""
- const passwordMatch = await bcrypt.compare(
+ const password = body.password || ""
+ const password1Match = await bcrypt.compare(
+ password,
+ volunteer.password1.replace(/^\$2y/, "$2a")
+ )
+ if (!password1Match) {
+ const password2Match = await bcrypt.compare(
password,
- volunteer.password.replace(/^\$2y/, "$2a")
+ volunteer.password2.replace(/^\$2y/, "$2a")
)
- if (!passwordMatch) {
+ if (!password2Match) {
throw Error("Mauvais mot de passe pour cet email")
}
-
- const jwt = await getJwt(email)
-
- return {
- firstname: volunteer.firstname,
- jwt,
- }
}
-)
+
+ const jwt = await getJwt(volunteer.email)
+
+ return {
+ firstname: volunteer.firstname,
+ 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 {
+ 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 : ${password}
L'ancien fonctionne encore, si tu t'en rappelles.`,
+ }
+ await sgMail.send(msg)
+ }
+}
diff --git a/src/server/gsheets/wishes.ts b/src/server/gsheets/wishes.ts
index 3787dcd..f9be22d 100644
--- a/src/server/gsheets/wishes.ts
+++ b/src/server/gsheets/wishes.ts
@@ -8,9 +8,6 @@ const expressAccessor = new ExpressAccessors(
)
export const wishListGet = expressAccessor.listGet()
-
export const wishGet = expressAccessor.get()
-
export const wishAdd = expressAccessor.add()
-
export const wishSet = expressAccessor.set()
diff --git a/src/server/index.ts b/src/server/index.ts
index a21d181..bf22730 100755
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -19,7 +19,7 @@ import { secure } from "./secure"
import { javGameListGet } from "./gsheets/javGames"
import { wishListGet, wishAdd } from "./gsheets/wishes"
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"
const app = express()
@@ -56,6 +56,7 @@ app.post("/WishAdd", wishAdd)
app.post("/PreVolunteerAdd", preVolunteerAdd)
app.get("/PreVolunteerCountGet", preVolunteerCountGet)
app.post("/VolunteerLogin", volunteerLogin)
+app.post("/VolunteerForgot", volunteerForgot)
// Secured APIs
app.get("/VolunteerGet", secure as RequestHandler, volunteerGet)
diff --git a/src/services/accessors.ts b/src/services/accessors.ts
index 8b28597..75af8e8 100644
--- a/src/services/accessors.ts
+++ b/src/services/accessors.ts
@@ -7,12 +7,15 @@ export type ElementWithId = unknown & { id: number }
export type ElementTranslation = { [k in keyof Element]: string }
-export default function getServiceAccessors<
+export default class ServiceAccessors<
// eslint-disable-next-line @typescript-eslint/ban-types
ElementNoId extends object,
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
error?: Error
}> {
@@ -22,7 +25,7 @@ export default function getServiceAccessors<
}
return async (id: number): Promise => {
try {
- const { data } = await axios.get(`${config.API_URL}/${elementName}Get`, {
+ const { data } = await axios.get(`${config.API_URL}/${this.elementName}Get`, {
...axiosConfig,
params: { id },
})
@@ -33,7 +36,7 @@ export default function getServiceAccessors<
}
}
- function listGet(): () => Promise<{
+ listGet(): () => Promise<{
data?: Element[]
error?: Error
}> {
@@ -44,7 +47,7 @@ export default function getServiceAccessors<
return async (): Promise => {
try {
const { data } = await axios.get(
- `${config.API_URL}/${elementName}ListGet`,
+ `${config.API_URL}/${this.elementName}ListGet`,
axiosConfig
)
return { data }
@@ -55,7 +58,7 @@ export default function getServiceAccessors<
}
// eslint-disable-next-line @typescript-eslint/ban-types
- function add(): (volunteerWithoutId: ElementNoId) => Promise<{
+ add(): (volunteerWithoutId: ElementNoId) => Promise<{
data?: Element
error?: Error
}> {
@@ -66,7 +69,7 @@ export default function getServiceAccessors<
return async (volunteerWithoutId: ElementNoId): Promise => {
try {
const { data } = await axios.post(
- `${config.API_URL}/${elementName}Add`,
+ `${config.API_URL}/${this.elementName}Add`,
volunteerWithoutId,
axiosConfig
)
@@ -77,7 +80,7 @@ export default function getServiceAccessors<
}
}
- function set(): (volunteer: Element) => Promise<{
+ set(): (volunteer: Element) => Promise<{
data?: Element
error?: Error
}> {
@@ -88,7 +91,7 @@ export default function getServiceAccessors<
return async (volunteer: Element): Promise => {
try {
const { data } = await axios.post(
- `${config.API_URL}/${elementName}Set`,
+ `${config.API_URL}/${this.elementName}Set`,
volunteer,
axiosConfig
)
@@ -99,7 +102,7 @@ export default function getServiceAccessors<
}
}
- function countGet(): () => Promise<{
+ countGet(): () => Promise<{
data?: number
error?: Error
}> {
@@ -110,7 +113,7 @@ export default function getServiceAccessors<
return async (): Promise => {
try {
const { data } = await axios.get(
- `${config.API_URL}/${elementName}CountGet`,
+ `${config.API_URL}/${this.elementName}CountGet`,
axiosConfig
)
return { data }
@@ -120,18 +123,18 @@ export default function getServiceAccessors<
}
}
- function customPost(apiName: string): (params: any) => Promise<{
- data?: Element
+ customPost(apiName: string): (params: any) => Promise<{
+ data?: any
error?: Error
}> {
interface ElementGetResponse {
- data?: Element
+ data?: any
error?: Error
}
return async (params: any): Promise => {
try {
const { data } = await axios.post(
- `${config.API_URL}/${elementName}${apiName}`,
+ `${config.API_URL}/${this.elementName}${apiName}`,
params,
axiosConfig
)
@@ -144,6 +147,4 @@ export default function getServiceAccessors<
}
}
}
-
- return { listGet, get, set, add, countGet, customPost }
}
diff --git a/src/services/javGames.ts b/src/services/javGames.ts
index 7a8325e..e82f4e5 100644
--- a/src/services/javGames.ts
+++ b/src/services/javGames.ts
@@ -1,4 +1,4 @@
-import getServiceAccessors from "./accessors"
+import ServiceAccessors from "./accessors"
export class JavGame {
id = 0
@@ -54,9 +54,9 @@ const elementName = "JavGame"
export type JavGameWithoutId = Omit
-const { listGet, get, set, add } = getServiceAccessors(elementName)
+const serviceAccessors = new ServiceAccessors(elementName)
-export const javGameListGet = listGet()
-export const javGameGet = get()
-export const javGameAdd = add()
-export const javGameSet = set()
+export const javGameListGet = serviceAccessors.listGet()
+export const javGameGet = serviceAccessors.get()
+export const javGameAdd = serviceAccessors.add()
+export const javGameSet = serviceAccessors.set()
diff --git a/src/services/preVolunteers.ts b/src/services/preVolunteers.ts
index da0d7ac..4149b17 100644
--- a/src/services/preVolunteers.ts
+++ b/src/services/preVolunteers.ts
@@ -1,4 +1,4 @@
-import getServiceAccessors from "./accessors"
+import ServiceAccessors from "./accessors"
export class PreVolunteer {
id = 0
@@ -34,13 +34,10 @@ export const passwordMinLength = 4
export type PreVolunteerWithoutId = Omit
-const { listGet, get, set, add, countGet } = getServiceAccessors<
- PreVolunteerWithoutId,
- PreVolunteer
->(elementName)
+const serviceAccessors = new ServiceAccessors(elementName)
-export const preVolunteerListGet = listGet()
-export const preVolunteerGet = get()
-export const preVolunteerAdd = add()
-export const preVolunteerSet = set()
-export const preVolunteerCountGet = countGet()
+export const preVolunteerListGet = serviceAccessors.listGet()
+export const preVolunteerGet = serviceAccessors.get()
+export const preVolunteerAdd = serviceAccessors.add()
+export const preVolunteerSet = serviceAccessors.set()
+export const preVolunteerCountGet = serviceAccessors.countGet()
diff --git a/src/services/volunteers.ts b/src/services/volunteers.ts
index d7513f6..f0f9153 100644
--- a/src/services/volunteers.ts
+++ b/src/services/volunteers.ts
@@ -1,4 +1,4 @@
-import getServiceAccessors from "./accessors"
+import ServiceAccessors from "./accessors"
export class Volunteer {
id = 0
@@ -21,11 +21,11 @@ export class Volunteer {
active = 0
- comment = ""
+ created = new Date()
- timestamp = new Date()
+ password1 = ""
- password = ""
+ password2 = ""
}
export const translationVolunteer: { [k in keyof Volunteer]: string } = {
@@ -39,9 +39,9 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
adult: "majeur",
privileges: "privilege",
active: "actif",
- comment: "commentaire",
- timestamp: "horodatage",
- password: "passe",
+ created: "creation",
+ password1: "passe1",
+ password2: "passe2",
}
const elementName = "Volunteer"
@@ -52,16 +52,20 @@ export const passwordMinLength = 4
export type VolunteerWithoutId = Omit
-const accessors = getServiceAccessors(elementName)
-const { listGet, get, set, add } = accessors
+const serviceAccessors = new ServiceAccessors(elementName)
-export const volunteerListGet = listGet()
-export const volunteerGet = get()
-export const volunteerAdd = add()
-export const volunteerSet = set()
+export const volunteerListGet = serviceAccessors.listGet()
+export const volunteerGet = serviceAccessors.get()
+export const volunteerAdd = serviceAccessors.add()
+export const volunteerSet = serviceAccessors.set()
export interface VolunteerLogin {
firstname: 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")
diff --git a/src/services/wishes.ts b/src/services/wishes.ts
index e4265ce..1ec86a9 100644
--- a/src/services/wishes.ts
+++ b/src/services/wishes.ts
@@ -1,4 +1,4 @@
-import getServiceAccessors from "./accessors"
+import ServiceAccessors from "./accessors"
export class Wish {
id = 0
@@ -27,9 +27,9 @@ const elementName = "Wish"
export type WishWithoutId = Omit
-const { listGet, get, set, add } = getServiceAccessors(elementName)
+const serviceAccessors = new ServiceAccessors(elementName)
-export const wishListGet = listGet()
-export const wishGet = get()
-export const wishAdd = add()
-export const wishSet = set()
+export const wishListGet = serviceAccessors.listGet()
+export const wishGet = serviceAccessors.get()
+export const wishAdd = serviceAccessors.add()
+export const wishSet = serviceAccessors.set()
diff --git a/src/store/__tests__/volunteer.ts b/src/store/__tests__/volunteer.ts
index 6a35048..9492fcc 100644
--- a/src/store/__tests__/volunteer.ts
+++ b/src/store/__tests__/volunteer.ts
@@ -23,9 +23,9 @@ const mockData: Volunteer = {
adult: 1,
privileges: 0,
active: 0,
- comment: "",
- timestamp: new Date(0),
- password: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
+ created: new Date(0),
+ password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
+ password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
}
const { id } = mockData
const mockError = "Oops! Something went wrong."
diff --git a/src/store/__tests__/volunteerList.ts b/src/store/__tests__/volunteerList.ts
index 27e954f..9d1a3fd 100644
--- a/src/store/__tests__/volunteerList.ts
+++ b/src/store/__tests__/volunteerList.ts
@@ -25,9 +25,9 @@ const mockData: Volunteer[] = [
adult: 1,
privileges: 0,
active: 0,
- comment: "",
- timestamp: new Date(0),
- password: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
+ created: new Date(0),
+ password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
+ password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
},
]
const mockError = "Oops! Something went wrong."
diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts
index 3b613dc..587927b 100644
--- a/src/store/rootReducer.ts
+++ b/src/store/rootReducer.ts
@@ -9,6 +9,7 @@ import volunteerAdd from "./volunteerAdd"
import volunteerList from "./volunteerList"
import volunteerSet from "./volunteerSet"
import volunteerLogin from "./volunteerLogin"
+import volunteerForgot from "./volunteerForgot"
import preVolunteerAdd from "./preVolunteerAdd"
import preVolunteerCount from "./preVolunteerCount"
@@ -23,6 +24,7 @@ export default (history: History) => ({
volunteerList,
volunteerSet,
volunteerLogin,
+ volunteerForgot,
preVolunteerAdd,
preVolunteerCount,
router: connectRouter(history) as any,
diff --git a/src/store/utils.ts b/src/store/utils.ts
index 5729229..44659ad 100644
--- a/src/store/utils.ts
+++ b/src/store/utils.ts
@@ -51,14 +51,10 @@ export function elementFetch(
if (error) {
dispatch(getFailure(error.message))
- if (errorMessage) {
- errorMessage(error)
- }
+ errorMessage?.(error)
} else {
dispatch(getSuccess(data as Element))
- if (successMessage) {
- successMessage(data as Element)
- }
+ successMessage?.(data as Element)
}
}
}
@@ -71,12 +67,8 @@ export function elementAddFetch(
getRequesting: ActionCreatorWithoutPayload,
getSuccess: ActionCreatorWithPayload,
getFailure: ActionCreatorWithPayload,
- errorMessage: (error: Error) => void = (_error) => {
- /* Meant to be empty */
- },
- successMessage: () => void = () => {
- /* Meant to be empty */
- }
+ errorMessage?: (error: Error) => void,
+ successMessage?: () => void
): (volunteerWithoutId: Omit) => AppThunk {
return (volunteerWithoutId: Omit): AppThunk =>
async (dispatch) => {
@@ -86,10 +78,10 @@ export function elementAddFetch(
if (error) {
dispatch(getFailure(error.message))
- errorMessage(error)
+ errorMessage?.(error)
} else {
dispatch(getSuccess(data as Element))
- successMessage()
+ successMessage?.()
}
}
}
@@ -102,12 +94,8 @@ export function elementListFetch(
getRequesting: ActionCreatorWithoutPayload,
getSuccess: ActionCreatorWithPayload,
getFailure: ActionCreatorWithPayload,
- errorMessage: (error: Error) => void = (_error) => {
- /* Meant to be empty */
- },
- successMessage: () => void = () => {
- /* Meant to be empty */
- }
+ errorMessage?: (error: Error) => void,
+ successMessage?: () => void
): () => AppThunk {
return (): AppThunk => async (dispatch) => {
dispatch(getRequesting())
@@ -116,10 +104,10 @@ export function elementListFetch(
if (error) {
dispatch(getFailure(error.message))
- errorMessage(error)
+ errorMessage?.(error)
} else {
dispatch(getSuccess(data as Element[]))
- successMessage()
+ successMessage?.()
}
}
}
@@ -132,12 +120,8 @@ export function elementSet(
getRequesting: ActionCreatorWithoutPayload,
getSuccess: ActionCreatorWithPayload,
getFailure: ActionCreatorWithPayload,
- errorMessage: (error: Error) => void = (_error) => {
- /* Meant to be empty */
- },
- successMessage: () => void = () => {
- /* Meant to be empty */
- }
+ errorMessage?: (error: Error) => void,
+ successMessage?: () => void
): (element: Element) => AppThunk {
return (element: Element): AppThunk =>
async (dispatch) => {
@@ -147,10 +131,10 @@ export function elementSet(
if (error) {
dispatch(getFailure(error.message))
- errorMessage(error)
+ errorMessage?.(error)
} else {
dispatch(getSuccess(data as Element))
- successMessage()
+ successMessage?.()
}
}
}
@@ -163,12 +147,8 @@ export function elementValueFetch(
getRequesting: ActionCreatorWithoutPayload,
getSuccess: ActionCreatorWithPayload,
getFailure: ActionCreatorWithPayload,
- errorMessage: (error: Error) => void = (_error) => {
- /* Meant to be empty */
- },
- successMessage: () => void = () => {
- /* Meant to be empty */
- }
+ errorMessage?: (error: Error) => void,
+ successMessage?: () => void
): () => AppThunk {
return (): AppThunk => async (dispatch) => {
dispatch(getRequesting())
@@ -177,10 +157,10 @@ export function elementValueFetch(
if (error) {
dispatch(getFailure(error.message))
- errorMessage(error)
+ errorMessage?.(error)
} else {
dispatch(getSuccess(data as Element))
- successMessage()
+ successMessage?.()
}
}
}
diff --git a/src/store/volunteerForgot.ts b/src/store/volunteerForgot.ts
new file mode 100644
index 0000000..a7e2435
--- /dev/null
+++ b/src/store/volunteerForgot.ts
@@ -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) => ({
+ readyStatus: "success",
+ entity: payload,
+ }),
+ getFailure: (_, { payload }: PayloadAction) => ({
+ readyStatus: "failure",
+ error: payload,
+ }),
+ },
+})
+
+export default volunteerForgotSlice.reducer
+export const { getRequesting, getSuccess, getFailure } = volunteerForgotSlice.actions
+
+export const fetchVolunteerForgot = elementFetch(
+ volunteerForgot,
+ getRequesting,
+ getSuccess,
+ getFailure
+)
diff --git a/src/store/volunteerLogin.ts b/src/store/volunteerLogin.ts
index 953c766..5b727e0 100644
--- a/src/store/volunteerLogin.ts
+++ b/src/store/volunteerLogin.ts
@@ -11,7 +11,7 @@ export const initialState: StateVolunteer = {
}
const volunteerLoginSlice = createSlice({
- name: "volunteer",
+ name: "volunteerLogin",
initialState,
reducers: {
getRequesting: (_) => ({
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
index 56627ad..5b6908b 100644
--- a/src/types/index.d.ts
+++ b/src/types/index.d.ts
@@ -3,6 +3,7 @@ declare const __SERVER__: boolean
declare const __DEV__: boolean
declare const __LOCAL__: boolean
declare const __TEST__: boolean
+declare const __SENDGRID_API_KEY__: string
declare module "*.svg"
declare module "*.gif"
@@ -20,6 +21,7 @@ declare namespace NodeJS {
__DEV__: boolean
__LOCAL__: boolean
__TEST__: boolean
+ __SENDGRID_API_KEY__: string
$RefreshReg$: () => void
$RefreshSig$$: () => void
}
diff --git a/yarn.lock b/yarn.lock
index f7c6bbe..f28276d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1334,6 +1334,29 @@
redux-thunk "^2.3.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":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"