@@ -253,4 +253,4 @@ const RegisterForm = ({ dispatch, preVolunteerCount }: Props): JSX.Element => {
)
}
-export default memo(RegisterForm)
+export default memo(PreRegisterForm)
diff --git a/src/components/RegisterForm/styles.module.scss b/src/components/PreRegisterForm/styles.module.scss
similarity index 98%
rename from src/components/RegisterForm/styles.module.scss
rename to src/components/PreRegisterForm/styles.module.scss
index 41736ad..e6e854c 100755
--- a/src/components/RegisterForm/styles.module.scss
+++ b/src/components/PreRegisterForm/styles.module.scss
@@ -1,7 +1,7 @@
@import "../../theme/variables";
@import "../../theme/mixins";
-.registerIntro {
+.preRegisterIntro {
dt {
font-weight: bold;
margin-top: 10px;
diff --git a/src/components/VolunteerInfo/__tests__/VolunteerInfo.tsx b/src/components/VolunteerInfo/__tests__/VolunteerInfo.tsx
index 787d2dc..7f361c8 100755
--- a/src/components/VolunteerInfo/__tests__/VolunteerInfo.tsx
+++ b/src/components/VolunteerInfo/__tests__/VolunteerInfo.tsx
@@ -3,6 +3,7 @@
*/
import { render } from "@testing-library/react"
import { MemoryRouter } from "react-router-dom"
+import { volunteerExample } from "../../../services/volunteers"
import VolunteerInfo from "../index"
@@ -10,23 +11,7 @@ describe("", () => {
it("renders", () => {
const tree = render(
-
+
).container.firstChild
diff --git a/src/components/VolunteerList/__tests__/VolunteerList.tsx b/src/components/VolunteerList/__tests__/VolunteerList.tsx
index 4f05e07..e075bff 100755
--- a/src/components/VolunteerList/__tests__/VolunteerList.tsx
+++ b/src/components/VolunteerList/__tests__/VolunteerList.tsx
@@ -4,33 +4,14 @@
import { render } from "@testing-library/react"
import { MemoryRouter } from "react-router-dom"
+import { volunteerExample } from "../../../services/volunteers"
import VolunteerList from "../index"
describe("", () => {
it("renders", () => {
const tree = render(
-
+
).container.firstChild
diff --git a/src/components/VolunteerSet/__tests__/VolunteerSet.tsx b/src/components/VolunteerSet/__tests__/VolunteerSet.tsx
index 8d107bc..45ff57d 100644
--- a/src/components/VolunteerSet/__tests__/VolunteerSet.tsx
+++ b/src/components/VolunteerSet/__tests__/VolunteerSet.tsx
@@ -3,6 +3,7 @@
*/
import { render } from "@testing-library/react"
import { MemoryRouter } from "react-router-dom"
+import { volunteerExample } from "../../../services/volunteers"
import VolunteerSet from "../index"
@@ -11,24 +12,7 @@ describe("", () => {
const dispatch = jest.fn()
const tree = render(
-
+
).container.firstChild
diff --git a/src/components/index.ts b/src/components/index.ts
index 114c892..e38540b 100755
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -1,3 +1,5 @@
+import LoginForm from "./LoginForm"
+import Notifications from "./Notifications"
import VolunteerList from "./VolunteerList"
import JavGameList from "./JavGameList"
import VolunteerInfo from "./VolunteerInfo"
@@ -5,9 +7,11 @@ import VolunteerSet from "./VolunteerSet"
import ErrorBoundary from "./ErrorBoundary"
import Loading from "./Loading"
import WishAdd from "./WishAdd"
-import RegisterForm from "./RegisterForm"
+import PreRegisterForm from "./PreRegisterForm"
export {
+ LoginForm,
+ Notifications,
VolunteerList,
JavGameList,
VolunteerInfo,
@@ -15,5 +19,5 @@ export {
ErrorBoundary,
Loading,
WishAdd,
- RegisterForm,
+ PreRegisterForm,
}
diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx
new file mode 100644
index 0000000..7a9583a
--- /dev/null
+++ b/src/pages/Home/Home.tsx
@@ -0,0 +1,50 @@
+import { FC, memo } from "react"
+import { RouteComponentProps, Link } from "react-router-dom"
+import { useDispatch, useSelector, shallowEqual } from "react-redux"
+import { Helmet } from "react-helmet"
+
+import { AppState, AppThunk } from "../../store"
+import { LoginForm, Notifications } from "../../components"
+import styles from "./styles.module.scss"
+import { fetchVolunteerNotifsSetIfNeed } from "../../store/volunteerNotifsSet"
+
+export type Props = RouteComponentProps
+
+const HomePage: FC = (): JSX.Element => {
+ const dispatch = useDispatch()
+ const readyStatus = useSelector((state: AppState) => state.volunteerNotifsSet.readyStatus)
+ const volunteerNotifs = useSelector(
+ (state: AppState) => state.volunteerNotifsSet.entity,
+ shallowEqual
+ )
+
+ const loginError = useSelector((state: AppState) => state.volunteerLogin.error, shallowEqual)
+ const jwt = useSelector((state: AppState) => state.auth.jwt, shallowEqual)
+
+ if (!readyStatus || readyStatus === "idle" || readyStatus === "request")
+ return Loading...
+
+ if (jwt) {
+ return
+ }
+ return (
+
+
+
+
+ S'informer sur le bénévolat
+
+
+
+ )
+}
+
+// Fetch server-side data here
+export const loadData = (): AppThunk[] => [fetchVolunteerNotifsSetIfNeed()]
+
+export default memo(HomePage)
diff --git a/src/pages/Home/HomePage.tsx b/src/pages/Home/HomePage.tsx
deleted file mode 100755
index 9e95112..0000000
--- a/src/pages/Home/HomePage.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { FC, memo } from "react"
-import { RouteComponentProps } from "react-router-dom"
-import { Helmet } from "react-helmet"
-
-import { AppThunk } from "../../store"
-import styles from "./styles.module.scss"
-
-export type Props = RouteComponentProps
-
-const fetchUserData = () => () => Promise.resolve()
-
-const HomePage: FC = (): JSX.Element => (
-
-)
-
-// Fetch server-side data here
-export const loadData = (): AppThunk[] => [
- fetchUserData(),
- // More pre-fetched actions...
-]
-
-export default memo(HomePage)
diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx
index 0e32223..f1cf6b7 100755
--- a/src/pages/Home/index.tsx
+++ b/src/pages/Home/index.tsx
@@ -1,15 +1,16 @@
import loadable from "@loadable/component"
import { Loading, ErrorBoundary } from "../../components"
-import { Props, loadData } from "./HomePage"
+import { Props, loadData } from "./Home"
-const Home = loadable(() => import("./HomePage"), {
+const HomePage = loadable(() => import("./Home"), {
fallback: ,
})
export default (props: Props): JSX.Element => (
-
+
)
+
export { loadData }
diff --git a/src/pages/Home/styles.module.scss b/src/pages/Home/styles.module.scss
index 1423793..0cab755 100755
--- a/src/pages/Home/styles.module.scss
+++ b/src/pages/Home/styles.module.scss
@@ -4,6 +4,14 @@
@include page-wrapper-center;
}
-.homeContent {
- @include page-content-wrapper(600px);
+.notificationsContent {
+ @include page-content-wrapper;
+}
+
+.loginContent {
+ @include page-content-wrapper;
+}
+
+.preRegisterContent {
+ @include page-content-wrapper;
}
diff --git a/src/pages/Login/LoginPage.tsx b/src/pages/Login/LoginPage.tsx
index 316bb0b..9f0dac0 100644
--- a/src/pages/Login/LoginPage.tsx
+++ b/src/pages/Login/LoginPage.tsx
@@ -4,7 +4,7 @@ import React, { memo } from "react"
import { Helmet } from "react-helmet"
import { AppState } from "../../store"
-import LoginForm from "../../components/LoginForm/LoginForm"
+import { LoginForm } from "../../components"
import styles from "./styles.module.scss"
export type Props = RouteComponentProps
diff --git a/src/pages/Register/RegisterPage.tsx b/src/pages/PreRegister/PreRegister.tsx
similarity index 77%
rename from src/pages/Register/RegisterPage.tsx
rename to src/pages/PreRegister/PreRegister.tsx
index f2ffd66..a262c5d 100644
--- a/src/pages/Register/RegisterPage.tsx
+++ b/src/pages/PreRegister/PreRegister.tsx
@@ -5,7 +5,7 @@ import { Helmet } from "react-helmet"
import { AppState, AppThunk, ValueRequest } from "../../store"
import { fetchPreVolunteerCountIfNeed } from "../../store/preVolunteerCount"
-import { RegisterForm } from "../../components"
+import { PreRegisterForm } from "../../components"
import styles from "./styles.module.scss"
export type Props = RouteComponentProps
@@ -29,14 +29,14 @@ function useList(
if (readyStatus === "failure") return Oops, Failed to load!
- return
+ return
}
}
-const RegisterPage: FC = (): JSX.Element => (
-
-
-
+const PreRegisterPage: FC
= (): JSX.Element => (
+
+
+
{useList((state: AppState) => state.preVolunteerCount, fetchPreVolunteerCountIfNeed)()}
@@ -45,4 +45,4 @@ const RegisterPage: FC = (): JSX.Element => (
// Fetch server-side data here
export const loadData = (): AppThunk[] => [fetchPreVolunteerCountIfNeed()]
-export default memo(RegisterPage)
+export default memo(PreRegisterPage)
diff --git a/src/pages/Register/index.tsx b/src/pages/PreRegister/index.tsx
similarity index 57%
rename from src/pages/Register/index.tsx
rename to src/pages/PreRegister/index.tsx
index a8f3dc2..0c8f838 100755
--- a/src/pages/Register/index.tsx
+++ b/src/pages/PreRegister/index.tsx
@@ -1,14 +1,16 @@
import loadable from "@loadable/component"
import { Loading, ErrorBoundary } from "../../components"
-import { Props } from "./RegisterPage"
+import { Props, loadData } from "./PreRegister"
-const RegisterPage = loadable(() => import("./RegisterPage"), {
+const PreRegister = loadable(() => import("./PreRegister"), {
fallback: ,
})
export default (props: Props): JSX.Element => (
-
+
)
+
+export { loadData }
diff --git a/src/pages/Register/styles.module.scss b/src/pages/PreRegister/styles.module.scss
similarity index 73%
rename from src/pages/Register/styles.module.scss
rename to src/pages/PreRegister/styles.module.scss
index c7f3efb..7d678c1 100755
--- a/src/pages/Register/styles.module.scss
+++ b/src/pages/PreRegister/styles.module.scss
@@ -1,9 +1,9 @@
@import "../../theme/mixins";
-.registerPage {
+.preRegisterPage {
@include page-wrapper-center;
}
-.registerContent {
+.preRegisterContent {
@include page-content-wrapper(600px);
}
diff --git a/src/routes/index.ts b/src/routes/index.ts
index 2cd6af8..11082a4 100755
--- a/src/routes/index.ts
+++ b/src/routes/index.ts
@@ -2,11 +2,11 @@ import { RouteConfig } from "react-router-config"
import App from "../app"
import AsyncHome, { loadData as loadHomeData } from "../pages/Home"
+import AsyncPreRegisterPage, { loadData as loadPreRegisterPage } from "../pages/PreRegister"
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"
export default [
@@ -16,7 +16,13 @@ export default [
{
path: "/",
exact: true,
- component: Register,
+ component: AsyncHome,
+ loadData: loadHomeData,
+ },
+ {
+ path: "/preRegister",
+ component: AsyncPreRegisterPage,
+ loadData: loadPreRegisterPage,
},
{
path: "/VolunteerPage/:id",
@@ -31,11 +37,6 @@ export default [
path: "/forgot",
component: Forgot,
},
- {
- path: "/home",
- component: AsyncHome,
- loadData: loadHomeData,
- },
{
path: "/wish",
component: AsyncWish,
diff --git a/src/server/gsheets/expressAccessors.ts b/src/server/gsheets/expressAccessors.ts
index 8d53844..e03a394 100644
--- a/src/server/gsheets/expressAccessors.ts
+++ b/src/server/gsheets/expressAccessors.ts
@@ -37,7 +37,7 @@ export default class ExpressAccessors<
}
// custom can be async
- get(custom?: (list: Element[], body: Request["body"]) => Promise | any) {
+ get(custom?: (list: Element[], body: Request["body"], id: number) => Promise | any) {
return async (request: Request, response: Response, _next: NextFunction): Promise => {
try {
const list = (await this.sheet.getList()) || []
@@ -46,7 +46,12 @@ export default class ExpressAccessors<
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)
+ const memberId = response?.locals?.jwt?.id || -1
+ toCaller = await custom(list, request.body, memberId)
+ if (toCaller?.jwt && toCaller?.id) {
+ response.cookie("jwt", toCaller.jwt, { maxAge: 365 * 24 * 60 * 60 })
+ response.cookie("id", toCaller.id, { maxAge: 365 * 24 * 60 * 60 })
+ }
}
response.status(200).json(toCaller)
} catch (e: any) {
@@ -72,7 +77,8 @@ export default class ExpressAccessors<
set(
custom?: (
list: Element[],
- body: RequestBody
+ body: RequestBody,
+ id: number
) => Promise> | CustomSetReturn
) {
return async (request: Request, response: Response, _next: NextFunction): Promise => {
@@ -81,8 +87,9 @@ export default class ExpressAccessors<
await this.sheet.set(request.body)
response.status(200)
} else {
+ const memberId = response?.locals?.jwt?.id || -1
const list = (await this.sheet.getList()) || []
- const { toDatabase, toCaller } = await custom(list, request.body)
+ const { toDatabase, toCaller } = await custom(list, request.body, memberId)
if (toDatabase !== undefined) {
await this.sheet.set(toDatabase)
}
diff --git a/src/server/gsheets/volunteers.ts b/src/server/gsheets/volunteers.ts
index 83c45e7..f767f09 100644
--- a/src/server/gsheets/volunteers.ts
+++ b/src/server/gsheets/volunteers.ts
@@ -3,7 +3,13 @@ import bcrypt from "bcrypt"
import sgMail from "@sendgrid/mail"
import ExpressAccessors, { RequestBody } from "./expressAccessors"
-import { Volunteer, VolunteerWithoutId, translationVolunteer } from "../../services/volunteers"
+import {
+ Volunteer,
+ VolunteerWithoutId,
+ VolunteerLogin,
+ VolunteerNotifs,
+ translationVolunteer,
+} from "../../services/volunteers"
import { canonicalEmail } from "../../utils/standardization"
import { getJwt } from "../secure"
@@ -14,38 +20,39 @@ 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.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")
- }
+export const volunteerLogin = expressAccessor.get(
+ async (list: Volunteer[], body: RequestBody): Promise => {
+ 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 password1Match = await bcrypt.compare(
- password,
- volunteer.password1.replace(/^\$2y/, "$2a")
- )
- if (!password1Match) {
- const password2Match = await bcrypt.compare(
+ const password = body.password || ""
+ const password1Match = await bcrypt.compare(
password,
- volunteer.password2.replace(/^\$2y/, "$2a")
+ volunteer.password1.replace(/^\$2y/, "$2a")
)
- if (!password2Match) {
- throw Error("Mauvais mot de passe pour cet email")
+ if (!password1Match) {
+ const password2Match = await bcrypt.compare(
+ password,
+ volunteer.password2.replace(/^\$2y/, "$2a")
+ )
+ if (!password2Match) {
+ throw Error("Mauvais mot de passe pour cet email")
+ }
+ }
+
+ const jwt = await getJwt(volunteer.id)
+
+ return {
+ id: volunteer.id,
+ 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) => {
@@ -78,6 +85,29 @@ export const volunteerForgot = expressAccessor.set(async (list: Volunteer[], bod
}
})
+export const volunteerNotifsSet = expressAccessor.set(
+ async (list: Volunteer[], body: RequestBody, id: number) => {
+ const volunteer = list.find((v) => v.id === id)
+ if (!volunteer) {
+ throw Error(`Il n'y a aucun bénévole avec cet identifiant ${id}`)
+ }
+ const newVolunteer = _.cloneDeep(volunteer)
+
+ _.assign(newVolunteer, _.pick(body[1], _.keys(newVolunteer)))
+
+ return {
+ toDatabase: newVolunteer,
+ toCaller: {
+ id: newVolunteer.id,
+ firstname: newVolunteer.firstname,
+ adult: newVolunteer.adult,
+ active: newVolunteer.active,
+ hiddenNotifs: newVolunteer.hiddenNotifs,
+ } as VolunteerNotifs,
+ }
+ }
+)
+
function getByEmail(list: Volunteer[], rawEmail: string): Volunteer | undefined {
const email = canonicalEmail(rawEmail || "")
const volunteer = list.find((v) => canonicalEmail(v.email) === email)
diff --git a/src/server/index.ts b/src/server/index.ts
index bf22730..c2522d5 100755
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,6 +1,7 @@
import path from "path"
import express, { RequestHandler } from "express"
import logger from "morgan"
+import cookieParser from "cookie-parser"
import compression from "compression"
import helmet from "helmet"
import hpp from "hpp"
@@ -19,7 +20,12 @@ 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, volunteerForgot } from "./gsheets/volunteers"
+import {
+ volunteerNotifsSet,
+ volunteerSet,
+ volunteerLogin,
+ volunteerForgot,
+} from "./gsheets/volunteers"
import config from "../config"
const app = express()
@@ -45,6 +51,7 @@ app.use(express.static(path.resolve(process.cwd(), "public")))
if (__DEV__) devServer(app)
app.use(express.json())
+app.use(cookieParser())
/**
* APIs
@@ -59,8 +66,9 @@ app.post("/VolunteerLogin", volunteerLogin)
app.post("/VolunteerForgot", volunteerForgot)
// Secured APIs
-app.get("/VolunteerGet", secure as RequestHandler, volunteerGet)
app.post("/VolunteerSet", secure as RequestHandler, volunteerSet)
+// UNSAFE app.post("/VolunteerGet", secure as RequestHandler, volunteerGet)
+app.post("/VolunteerNotifsSet", secure as RequestHandler, volunteerNotifsSet)
// Use React server-side rendering middleware
app.get("*", ssr)
diff --git a/src/server/secure.ts b/src/server/secure.ts
index e0481fe..04210c5 100644
--- a/src/server/secure.ts
+++ b/src/server/secure.ts
@@ -2,7 +2,6 @@ import { NextFunction, Request, Response } from "express"
import path from "path"
import { promises as fs } from "fs"
import { verify, sign } from "jsonwebtoken"
-import { canonicalEmail } from "../utils/standardization"
import config from "../config"
@@ -20,16 +19,17 @@ export function secure(request: AuthorizedRequest, response: Response, next: Nex
}
const rawToken = request.headers.authorization
- const token = rawToken && rawToken.split(/\s/)[1]
+ const token1 = rawToken && rawToken.split(/\s/)[1]
+ const token2 = request.cookies?.jwt
+ const token = token1 || token2
verify(token, cachedSecret, (tokenError: any, decoded: any) => {
if (tokenError) {
- response.status(403).json({
- error: "Invalid token, please Log in first, criterion auth",
+ response.status(200).json({
+ error: "Accès interdit sans identification",
})
return
}
- decoded.user.replace(/@gmailcom$/, "@gmail.com")
response.locals.jwt = decoded
next()
})
@@ -50,9 +50,9 @@ async function getSecret() {
return cachedSecret
}
-export async function getJwt(email: string): Promise {
+export async function getJwt(id: number): Promise {
const jwt = sign(
- { user: canonicalEmail(email), permissions: [] },
+ { id },
await getSecret()
// __TEST__
// ? undefined
diff --git a/src/server/ssr.tsx b/src/server/ssr.tsx
index 7ddbdac..fbc87a9 100644
--- a/src/server/ssr.tsx
+++ b/src/server/ssr.tsx
@@ -12,9 +12,11 @@ import { Action } from "@reduxjs/toolkit"
import createStore from "../store"
import renderHtml from "./renderHtml"
import routes from "../routes"
+import { getCookieJWT } from "../services/auth"
export default async (req: Request, res: Response, next: NextFunction): Promise => {
- const { store } = createStore({ url: req.url })
+ const { jwt, id } = getCookieJWT(req.headers.cookie)
+ const { store } = createStore({ url: req.url, jwt, id })
// The method for loading data from server-side
const loadBranchData = (): Promise => {
diff --git a/src/services/accessors.ts b/src/services/accessors.ts
index 75af8e8..d84b151 100644
--- a/src/services/accessors.ts
+++ b/src/services/accessors.ts
@@ -1,4 +1,5 @@
import axios from "axios"
+import _ from "lodash"
import config from "../config"
import { axiosConfig } from "./auth"
@@ -123,7 +124,9 @@ export default class ServiceAccessors<
}
}
- customPost(apiName: string): (params: any) => Promise<{
+ customPost>(
+ apiName: string
+ ): (...params: InputElements) => Promise<{
data?: any
error?: Error
}> {
@@ -131,7 +134,7 @@ export default class ServiceAccessors<
data?: any
error?: Error
}
- return async (params: any): Promise => {
+ return async (...params: InputElements): Promise => {
try {
const { data } = await axios.post(
`${config.API_URL}/${this.elementName}${apiName}`,
@@ -147,4 +150,36 @@ export default class ServiceAccessors<
}
}
}
+
+ securedCustomPost>(
+ apiName: string
+ ): (
+ jwt: string,
+ ...params: InputElements
+ ) => Promise<{
+ data?: any
+ error?: Error
+ }> {
+ interface ElementGetResponse {
+ data?: any
+ error?: Error
+ }
+ return async (jwt: string, ...params: InputElements): Promise => {
+ try {
+ const auth = { headers: { Authorization: `Bearer ${jwt}` } }
+ const fullAxiosConfig = _.defaultsDeep(auth, axiosConfig)
+ const { data } = await axios.post(
+ `${config.API_URL}/${this.elementName}${apiName}`,
+ params,
+ fullAxiosConfig
+ )
+ if (data.error) {
+ throw Error(data.error)
+ }
+ return { data }
+ } catch (error) {
+ return { error: error as Error }
+ }
+ }
+ }
}
diff --git a/src/services/auth.ts b/src/services/auth.ts
index 5fb7332..bb06259 100644
--- a/src/services/auth.ts
+++ b/src/services/auth.ts
@@ -1,4 +1,7 @@
import { AxiosRequestConfig } from "axios"
+import Cookies from "js-cookie"
+
+import { VolunteerLogin } from "./volunteers"
const storage: any = localStorage
@@ -6,17 +9,30 @@ export const axiosConfig: AxiosRequestConfig = {
headers: {},
}
-const jwt: string | null = storage?.getItem("id_token")
-if (jwt) {
- setJWT(jwt)
-}
-
-export function setJWT(token: string): void {
+export function setJWT(token: string, id: number): void {
axiosConfig.headers.Authorization = `Bearer ${token}`
- storage?.setItem("id_token", token)
+ storage?.setItem("jwt", token)
+ storage?.setItem("id", id)
+ Cookies.set("jwt", token)
+ Cookies.set("id", `${id}`)
}
export function unsetJWT(): void {
delete axiosConfig.headers.Authorization
- storage?.removeItem("id_token")
+ storage?.removeItem("jwt")
+ storage?.removeItem("id")
+
+ Cookies.remove("jwt")
+ Cookies.remove("id")
+}
+
+export function getCookieJWT(cookie = ""): VolunteerLogin {
+ const cookies = cookie
+ .split(";")
+ .reduce((res: { [cookieName: string]: string }, el: string) => {
+ const [k, v] = el.split("=")
+ res[k.trim()] = v
+ return res
+ }, {})
+ return { jwt: cookies.jwt, id: +cookies.id }
}
diff --git a/src/services/volunteers.ts b/src/services/volunteers.ts
index f0f9153..76592cf 100644
--- a/src/services/volunteers.ts
+++ b/src/services/volunteers.ts
@@ -19,7 +19,9 @@ export class Volunteer {
privileges = 0
- active = 0
+ active = ""
+
+ hiddenNotifs: number[] = []
created = new Date()
@@ -39,6 +41,7 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
adult: "majeur",
privileges: "privilege",
active: "actif",
+ hiddenNotifs: "notifsCachees",
created: "creation",
password1: "passe1",
password2: "passe2",
@@ -46,6 +49,23 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
const elementName = "Volunteer"
+export const volunteerExample: Volunteer = {
+ id: 1,
+ firstname: "Aupeix",
+ lastname: "Amélie",
+ email: "pakouille.lakouille@yahoo.fr",
+ mobile: "0675650392",
+ photo: "images/volunteers/$taille/amélie_aupeix.jpg",
+ food: "Végétarien",
+ adult: 1,
+ privileges: 0,
+ active: "inconnu",
+ hiddenNotifs: [],
+ created: new Date(0),
+ password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
+ password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
+}
+
export const emailRegexp =
/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i
export const passwordMinLength = 4
@@ -60,12 +80,23 @@ export const volunteerAdd = serviceAccessors.add()
export const volunteerSet = serviceAccessors.set()
export interface VolunteerLogin {
- firstname: string
+ id: number
jwt: string
}
-export const volunteerLogin = serviceAccessors.customPost("Login")
+export const volunteerLogin =
+ serviceAccessors.customPost<[{ email: string; password: string }]>("Login")
export interface VolunteerForgot {
message: string
}
-export const volunteerForgot = serviceAccessors.customPost("Forgot")
+export const volunteerForgot = serviceAccessors.customPost<[{ email: string }]>("Forgot")
+
+export interface VolunteerNotifs {
+ id: number
+ firstname: string
+ adult: number
+ active: string
+ hiddenNotifs: number[]
+}
+export const volunteerNotifsSet =
+ serviceAccessors.securedCustomPost<[number, Partial]>("NotifsSet")
diff --git a/src/store/__tests__/volunteer.ts b/src/store/__tests__/volunteer.ts
index 9492fcc..4a5f993 100644
--- a/src/store/__tests__/volunteer.ts
+++ b/src/store/__tests__/volunteer.ts
@@ -8,25 +8,11 @@ import volunteer, {
fetchVolunteer,
initialState,
} from "../volunteer"
-import { Volunteer } from "../../services/volunteers"
+import { Volunteer, volunteerExample } from "../../services/volunteers"
jest.mock("axios")
-const mockData: Volunteer = {
- id: 1,
- lastname: "Aupeix",
- firstname: "Amélie",
- email: "pakouille.lakouille@yahoo.fr",
- mobile: "0675650392",
- photo: "images/volunteers/$taille/amélie_aupeix.jpg",
- food: "Végétarien",
- adult: 1,
- privileges: 0,
- active: 0,
- created: new Date(0),
- password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
- password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
-}
+const mockData: Volunteer = volunteerExample
const { id } = mockData
const mockError = "Oops! Something went wrong."
diff --git a/src/store/__tests__/volunteerList.ts b/src/store/__tests__/volunteerList.ts
index 9d1a3fd..fa069f7 100644
--- a/src/store/__tests__/volunteerList.ts
+++ b/src/store/__tests__/volunteerList.ts
@@ -9,27 +9,11 @@ import volunteerList, {
getFailure,
fetchVolunteerList,
} from "../volunteerList"
-import { Volunteer } from "../../services/volunteers"
+import { Volunteer, volunteerExample } from "../../services/volunteers"
jest.mock("axios")
-const mockData: Volunteer[] = [
- {
- id: 1,
- lastname: "Aupeix",
- firstname: "Amélie",
- email: "pakouille.lakouille@yahoo.fr",
- mobile: "0675650392",
- photo: "images/volunteers/$taille/amélie_aupeix.jpg",
- food: "Végétarien",
- adult: 1,
- privileges: 0,
- active: 0,
- created: new Date(0),
- password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
- password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
- },
-]
+const mockData: Volunteer[] = [volunteerExample]
const mockError = "Oops! Something went wrong."
describe("volunteerList reducer", () => {
diff --git a/src/store/auth.ts b/src/store/auth.ts
new file mode 100644
index 0000000..4dc468c
--- /dev/null
+++ b/src/store/auth.ts
@@ -0,0 +1,35 @@
+import { createSlice, PayloadAction } from "@reduxjs/toolkit"
+import { AppState } from "."
+
+// Define a type for the slice state
+interface AuthState {
+ id: number
+ jwt: string
+}
+
+// Define the initial state using that type
+const initialState: AuthState = {
+ id: 0,
+ jwt: "",
+}
+
+export const auth = createSlice({
+ name: "auth",
+ initialState,
+ reducers: {
+ setCurrentUser: (state, action: PayloadAction) => {
+ state.id = action.payload.id
+ state.jwt = action.payload.jwt
+ },
+ logoutUser: (state) => {
+ state.id = 0
+ state.jwt = ""
+ },
+ },
+})
+
+export const { setCurrentUser, logoutUser } = auth.actions
+
+export const selectCount = (state: AppState): AuthState => state.auth
+
+export default auth.reducer
diff --git a/src/store/index.ts b/src/store/index.ts
index 191b432..af49c9f 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -2,18 +2,22 @@ import { createMemoryHistory, createBrowserHistory } from "history"
import { Action, configureStore, EntityState } from "@reduxjs/toolkit"
import { ThunkAction } from "redux-thunk"
import { routerMiddleware } from "connected-react-router"
+import Cookies from "js-cookie"
import createRootReducer from "./rootReducer"
import { StateRequest } from "./utils"
+import { setCurrentUser, logoutUser } from "./auth"
interface Arg {
initialState?: typeof window.__INITIAL_STATE__
url?: string
+ jwt?: string
+ id?: number
}
// Use inferred return type for making correctly Redux types
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-const createStore = ({ initialState, url }: Arg = {}) => {
+const createStore = ({ initialState, url, jwt, id }: Arg = {}) => {
const history = __SERVER__
? createMemoryHistory({ initialEntries: [url || "/"] })
: createBrowserHistory()
@@ -28,10 +32,20 @@ const createStore = ({ initialState, url }: Arg = {}) => {
devTools: __DEV__,
})
+ if (jwt && id) {
+ store.dispatch(setCurrentUser({ jwt, id }))
+ } else {
+ store.dispatch(logoutUser())
+ }
+
return { store, history }
}
-const { store } = createStore()
+const storage: any = localStorage
+const id = +(Cookies.get("id") || storage?.getItem("id"))
+const jwt = Cookies.get("jwt") || storage?.getItem("jwt")
+
+const { store } = createStore({ id, jwt })
export type AppState = ReturnType
diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts
index 587927b..90f0a81 100644
--- a/src/store/rootReducer.ts
+++ b/src/store/rootReducer.ts
@@ -1,6 +1,7 @@
import { History } from "history"
import { connectRouter } from "connected-react-router"
+import auth from "./auth"
import wishAdd from "./wishAdd"
import wishList from "./wishList"
import javGameList from "./javGameList"
@@ -10,12 +11,14 @@ import volunteerList from "./volunteerList"
import volunteerSet from "./volunteerSet"
import volunteerLogin from "./volunteerLogin"
import volunteerForgot from "./volunteerForgot"
+import volunteerNotifsSet from "./volunteerNotifsSet"
import preVolunteerAdd from "./preVolunteerAdd"
import preVolunteerCount from "./preVolunteerCount"
// Use inferred return type for making correctly Redux types
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export default (history: History) => ({
+ auth,
wishAdd,
wishList,
javGameList,
@@ -25,6 +28,7 @@ export default (history: History) => ({
volunteerSet,
volunteerLogin,
volunteerForgot,
+ volunteerNotifsSet,
preVolunteerAdd,
preVolunteerCount,
router: connectRouter(history) as any,
diff --git a/src/store/utils.ts b/src/store/utils.ts
index 44659ad..9ee4032 100644
--- a/src/store/utils.ts
+++ b/src/store/utils.ts
@@ -1,7 +1,7 @@
import { ActionCreatorWithoutPayload, ActionCreatorWithPayload } from "@reduxjs/toolkit"
import { toast } from "react-toastify"
-import { AppThunk } from "."
+import { AppThunk, AppDispatch } from "."
export interface StateRequest {
readyStatus: "idle" | "request" | "success" | "failure"
@@ -32,8 +32,8 @@ export function toastSuccess(message: string): void {
})
}
-export function elementFetch(
- elementService: (...idArgs: any[]) => Promise<{
+export function elementFetch>(
+ elementService: (...idArgs: ServiceInput) => Promise<{
data?: Element | undefined
error?: Error | undefined
}>,
@@ -41,9 +41,9 @@ export function elementFetch(
getSuccess: ActionCreatorWithPayload,
getFailure: ActionCreatorWithPayload,
errorMessage?: (error: Error) => void,
- successMessage?: (data: Element) => void
-): (...idArgs: any[]) => AppThunk {
- return (...idArgs: any[]): AppThunk =>
+ successMessage?: (data: Element, dispatch: AppDispatch) => void
+): (...idArgs: ServiceInput) => AppThunk {
+ return (...idArgs: ServiceInput): AppThunk =>
async (dispatch) => {
dispatch(getRequesting())
@@ -54,7 +54,7 @@ export function elementFetch(
errorMessage?.(error)
} else {
dispatch(getSuccess(data as Element))
- successMessage?.(data as Element)
+ successMessage?.(data as Element, dispatch)
}
}
}
diff --git a/src/store/volunteerLogin.ts b/src/store/volunteerLogin.ts
index 5b727e0..bc7a2c8 100644
--- a/src/store/volunteerLogin.ts
+++ b/src/store/volunteerLogin.ts
@@ -3,6 +3,9 @@ import { PayloadAction, createSlice } from "@reduxjs/toolkit"
import { StateRequest, elementFetch } from "./utils"
import { VolunteerLogin, volunteerLogin } from "../services/volunteers"
import { setJWT } from "../services/auth"
+import { AppDispatch } from "."
+import { setCurrentUser } from "./auth"
+import { fetchVolunteerNotifsSet } from "./volunteerNotifsSet"
type StateVolunteer = { entity?: VolunteerLogin } & StateRequest
@@ -31,13 +34,15 @@ const volunteerLoginSlice = createSlice({
export default volunteerLoginSlice.reducer
export const { getRequesting, getSuccess, getFailure } = volunteerLoginSlice.actions
-export const fetchVolunteerLogin = elementFetch(
+export const fetchVolunteerLogin = elementFetch>(
volunteerLogin,
getRequesting,
getSuccess,
getFailure,
undefined,
- (login: VolunteerLogin) => {
- setJWT(login.jwt)
+ (login: VolunteerLogin, dispatch: AppDispatch) => {
+ setJWT(login.jwt, login.id)
+ dispatch(setCurrentUser(login))
+ dispatch(fetchVolunteerNotifsSet(login.jwt, login.id, {}))
}
)
diff --git a/src/store/volunteerNotifsSet.ts b/src/store/volunteerNotifsSet.ts
new file mode 100644
index 0000000..09ea239
--- /dev/null
+++ b/src/store/volunteerNotifsSet.ts
@@ -0,0 +1,57 @@
+import { PayloadAction, createSlice } from "@reduxjs/toolkit"
+
+import { StateRequest, toastError, elementFetch } from "./utils"
+import { VolunteerNotifs, volunteerNotifsSet } from "../services/volunteers"
+import { AppThunk, AppState } from "."
+
+type StateVolunteerNotifsSet = { entity?: VolunteerNotifs } & StateRequest
+
+export const initialState: StateVolunteerNotifsSet = {
+ readyStatus: "idle",
+}
+
+const volunteerNotifsSetSlice = createSlice({
+ name: "volunteerNotifsSet",
+ initialState,
+ reducers: {
+ getRequesting: (_) => ({
+ readyStatus: "request",
+ }),
+ getSuccess: (_, { payload }: PayloadAction) => ({
+ readyStatus: "success",
+ entity: payload,
+ }),
+ getFailure: (_, { payload }: PayloadAction) => ({
+ readyStatus: "failure",
+ error: payload,
+ }),
+ },
+})
+
+export default volunteerNotifsSetSlice.reducer
+export const { getRequesting, getSuccess, getFailure } = volunteerNotifsSetSlice.actions
+
+export const fetchVolunteerNotifsSet = elementFetch(
+ volunteerNotifsSet,
+ getRequesting,
+ getSuccess,
+ getFailure,
+ (error: Error) => toastError(`Erreur lors du chargement des notifications: ${error.message}`)
+)
+
+const shouldFetchVolunteerNotifsSet = (state: AppState, id: number) =>
+ state.volunteerNotifsSet.readyStatus !== "success" ||
+ (state.volunteerNotifsSet.entity && state.volunteerNotifsSet.entity.id !== id)
+
+export const fetchVolunteerNotifsSetIfNeed =
+ (id = 0, notif: Partial = {}): AppThunk =>
+ (dispatch, getState) => {
+ let jwt = ""
+ if (!id) {
+ ;({ id, jwt } = getState().auth)
+ }
+ if (shouldFetchVolunteerNotifsSet(getState(), id))
+ return dispatch(fetchVolunteerNotifsSet(jwt, id, notif))
+
+ return null
+ }
diff --git a/yarn.lock b/yarn.lock
index f28276d..09f3529 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1566,6 +1566,13 @@
dependencies:
"@types/node" "*"
+"@types/cookie-parser@^1.4.2":
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.2.tgz#e4d5c5ffda82b80672a88a4281aaceefb1bd9df5"
+ integrity sha512-uwcY8m6SDQqciHsqcKDGbo10GdasYsPCYkH3hVegj9qAah6pX5HivOnOuI3WYmyQMnOATV39zv/Ybs0bC/6iVg==
+ dependencies:
+ "@types/express" "*"
+
"@types/css-minimizer-webpack-plugin@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.0.2.tgz#ed58bbbde8a7b7591118aa93d8f8d0cdc0cc6173"
@@ -1725,6 +1732,11 @@
jest-diff "^26.0.0"
pretty-format "^26.0.0"
+"@types/js-cookie@^3.0.1":
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.1.tgz#04aa743e2e0a85a22ee9aa61f6591a8bc19b5d68"
+ integrity sha512-7wg/8gfHltklehP+oyJnZrz9XBuX5ZPP4zB6UsI84utdlkRYLnOm2HfpLXazTwZA+fpGn0ir8tGNgVnMEleBGQ==
+
"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.9"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
@@ -3500,6 +3512,14 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
dependencies:
safe-buffer "~5.1.1"
+cookie-parser@^1.4.6:
+ version "1.4.6"
+ resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.6.tgz#3ac3a7d35a7a03bbc7e365073a26074824214594"
+ integrity sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==
+ dependencies:
+ cookie "0.4.1"
+ cookie-signature "1.0.6"
+
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
@@ -3510,6 +3530,11 @@ cookie@0.4.0:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
+cookie@0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
+ integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
+
core-js-compat@^3.18.0, core-js-compat@^3.19.1:
version "3.19.1"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.19.1.tgz#fe598f1a9bf37310d77c3813968e9f7c7bb99476"
@@ -6790,6 +6815,11 @@ js-base64@^2.1.8:
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==
+js-cookie@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414"
+ integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==
+
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"