Adds redux list add example

This commit is contained in:
forceoranj 2021-11-10 00:47:24 +01:00
parent 2616b109d7
commit 1ddb710a6c
19 changed files with 1809 additions and 1141 deletions

View File

@ -1,3 +1,4 @@
/* eslint-disable max-classes-per-file */
import * as _ from "lodash" import * as _ from "lodash"
import path from "path" import path from "path"
import { promises as fs } from "fs" import { promises as fs } from "fs"
@ -329,6 +330,20 @@ class Test {
tictactoe: boolean[] = [] tictactoe: boolean[] = []
} }
// class Membre {
// membreId = 0
// nom = ""
// prenom = ""
// mail = ""
// telephone = ""
// photo = ""
// }
// Can't run it on every test, it requires private access to a google sheet // Can't run it on every test, it requires private access to a google sheet
async function testGSheetAPi(): Promise<void> { async function testGSheetAPi(): Promise<void> {
const dataset: Test[] = [ const dataset: Test[] = [
@ -368,6 +383,15 @@ async function testGSheetAPi(): Promise<void> {
}, },
] ]
// console.log("Lecture des Membres...")
// const datasetMembresLu = await getList<Membre>("Membres", new Membre())
// if (!datasetMembresLu) {
// console.log("ECHEC de la lecture des membres", datasetMembresLu)
// return
// }
// console.log("Extraction des membres réussie")
// await fs.writeFile("membres.json", JSON.stringify(datasetMembresLu))
console.log("Test d'écriture...") console.log("Test d'écriture...")
const resultatEcriture = await setList<Test>("Tests de l'API", dataset) const resultatEcriture = await setList<Test>("Tests de l'API", dataset)
if (!resultatEcriture) { if (!resultatEcriture) {

View File

@ -99,6 +99,7 @@
"react-router": "^5.2.0", "react-router": "^5.2.0",
"react-router-config": "^5.1.1", "react-router-config": "^5.1.1",
"react-router-dom": "^5.3.0", "react-router-dom": "^5.3.0",
"react-toastify": "^8.1.0",
"readline": "^1.3.0", "readline": "^1.3.0",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"serialize-javascript": "^6.0.0", "serialize-javascript": "^6.0.0",

View File

@ -1,12 +1,14 @@
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
import { RouteConfig, renderRoutes } from "react-router-config" import { RouteConfig, renderRoutes } from "react-router-config"
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet"
import { ToastContainer } from "react-toastify"
import logo from "../static/logo.svg" import logo from "../static/logo.svg"
import config from "../config" import config from "../config"
// Import your global styles here // Import your global styles here
import "normalize.css/normalize.css" import "normalize.css/normalize.css"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
import "react-toastify/dist/ReactToastify.css"
interface Route { interface Route {
route: { routes: RouteConfig[] } route: { routes: RouteConfig[] }
@ -24,6 +26,7 @@ const App = ({ route }: Route): JSX.Element => (
<hr /> <hr />
{/* Child routes won't render without this */} {/* Child routes won't render without this */}
{renderRoutes(route.routes)} {renderRoutes(route.routes)}
<ToastContainer />
</div> </div>
) )

View File

@ -0,0 +1,20 @@
/**
* @jest-environment jsdom
*/
import { render } from "@testing-library/react"
import { MemoryRouter } from "react-router-dom"
import AddEnvie from "../index"
describe("<AddEnvie />", () => {
it("renders", () => {
const dispatch = jest.fn()
const tree = render(
<MemoryRouter>
<AddEnvie dispatch={dispatch} />
</MemoryRouter>
).container.firstChild
expect(tree).toMatchSnapshot()
})
})

View File

@ -0,0 +1,69 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<AddEnvie /> renders 1`] = `
<section
class="EnvieList"
>
<h2>
Ajouter une nouvelle envie
</h2>
<form>
<label
for="postDomaine"
>
Domaine:
<input
id="postDomaine"
name="postDomaine"
type="text"
value=""
/>
</label>
<label
for="postEnvies"
>
Envies:
<textarea
id="postEnvies"
name="postEnvies"
/>
</label>
<label
for="postPrecisions"
>
Precisions:
<textarea
id="postPrecisions"
name="postPrecisions"
/>
</label>
<label
for="postEquipes"
>
Equipes:
<input
id="postEquipes"
name="postEquipes"
type="text"
value=""
/>
</label>
<label
for="postDateAjout"
>
DateAjout:
<input
id="postDateAjout"
name="postDateAjout"
type="date"
value=""
/>
</label>
<button
type="button"
>
Save Post
</button>
</form>
</section>
`;

View File

@ -0,0 +1,117 @@
import React, { useState, memo } from "react"
import { toast } from "react-toastify"
import { AppDispatch } from "../../store"
import { postEnvie } from "../../store/envieAdd"
import styles from "./styles.module.scss"
interface Props {
dispatch: AppDispatch
}
const AddEnvie = ({ dispatch }: Props) => {
const [domaine, setDomaine] = useState("")
const [envies, setEnvies] = useState("")
const [precisions, setPrecisions] = useState("")
const [equipes, setEquipes] = useState([""])
const [dateAjout, setDateAjout] = useState("")
const onDomaineChanged = (e: React.ChangeEvent<HTMLInputElement>) => setDomaine(e.target.value)
const onEnviesChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => setEnvies(e.target.value)
const onPrecisionsChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) =>
setPrecisions(e.target.value)
const onEquipesChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
setEquipes(e.target.value.split(/, ?/))
const onDateAjoutChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
setDateAjout(e.target.value)
const onSavePostClicked = () => {
if (domaine && envies) {
dispatch(
postEnvie({
domaine,
envies,
precisions,
equipes,
dateAjout,
})
)
setDomaine("")
setEnvies("")
setPrecisions("")
setEquipes([""])
setDateAjout("")
} else {
toast.warning("Il faut au moins préciser un domaine et l'envie", {
position: "top-center",
autoClose: 6000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
})
}
}
return (
<section className={styles.EnvieList}>
<h2>Ajouter une nouvelle envie</h2>
<form>
<label htmlFor="postDomaine">
Domaine:
<input
type="text"
id="postDomaine"
name="postDomaine"
value={domaine}
onChange={onDomaineChanged}
/>
</label>
<label htmlFor="postEnvies">
Envies:
<textarea
id="postEnvies"
name="postEnvies"
value={envies}
onChange={onEnviesChanged}
/>
</label>
<label htmlFor="postPrecisions">
Precisions:
<textarea
id="postPrecisions"
name="postPrecisions"
value={precisions}
onChange={onPrecisionsChanged}
/>
</label>
<label htmlFor="postEquipes">
Equipes:
<input
type="text"
id="postEquipes"
name="postEquipes"
value={equipes.join(", ")}
onChange={onEquipesChanged}
/>
</label>
<label htmlFor="postDateAjout">
DateAjout:
<input
type="date"
id="postDateAjout"
name="postDateAjout"
value={dateAjout}
onChange={onDateAjoutChanged}
/>
</label>
<button type="button" onClick={onSavePostClicked}>
Save Post
</button>
</form>
</section>
)
}
export default memo(AddEnvie)

View File

@ -0,0 +1,17 @@
@import "../../theme/variables";
.jav-game-list {
color: $color-white;
ul {
padding-left: 17px;
li {
margin-bottom: 0.5em;
}
}
a {
color: $color-white;
}
}

View File

@ -3,5 +3,6 @@ import JeuxJavList from "./JeuxJavList"
import Info from "./Info" import Info from "./Info"
import ErrorBoundary from "./ErrorBoundary" import ErrorBoundary from "./ErrorBoundary"
import Loading from "./Loading" import Loading from "./Loading"
import AddEnvie from "./AddEnvie"
export { List, JeuxJavList, Info, ErrorBoundary, Loading } export { List, JeuxJavList, Info, ErrorBoundary, Loading, AddEnvie }

View File

@ -1,25 +1,33 @@
import { Request, Response, NextFunction } from "express" import { Request, Response, NextFunction } from "express"
import { getList, setList } from "./utils" import { getList, add } from "./utils"
import { Envie } from "../services/envies" import { Envie, EnvieWithoutId } from "../services/envies"
export const getEnvieList = async ( export const getEnvieList = async (
_request: Request, _request: Request,
response: Response, response: Response,
_next: NextFunction _next: NextFunction
): Promise<void> => { ): Promise<void> => {
try {
const list = await getList<Envie>("Envies d'aider", new Envie()) const list = await getList<Envie>("Envies d'aider", new Envie())
if (list) { if (list) {
response.status(200).json(list) response.status(200).json(list)
} }
} catch (e: unknown) {
response.status(400).json(e)
}
} }
export const setEnvieList = async ( export const addEnvie = async (
request: Request, request: Request,
response: Response, response: Response,
_next: NextFunction _next: NextFunction
): Promise<void> => { ): Promise<void> => {
const success = await setList<Envie>("Envies d'aider", request.body) try {
if (success) { const envie = await add<EnvieWithoutId, Envie>("Envies d'aider", "envieId", request.body)
response.status(200).json() if (envie) {
response.status(200).json(envie)
}
} catch (e: unknown) {
response.status(400).json(e)
} }
} }

View File

@ -8,10 +8,14 @@ export const getJeuxJavList = async (
response: Response, response: Response,
_next: NextFunction _next: NextFunction
): Promise<void> => { ): Promise<void> => {
try {
const list = await getList<JeuxJav>("Jeux JAV", new JeuxJav()) const list = await getList<JeuxJav>("Jeux JAV", new JeuxJav())
if (list) { if (list) {
response.status(200).json(list) response.status(200).json(list)
} }
} catch (e: unknown) {
response.status(400).json(e)
}
} }
export const getJeuxJavData = async ( export const getJeuxJavData = async (

View File

@ -18,8 +18,7 @@ export async function getList<Element extends object>(
const rows = (await sheet.getRows()) as StringifiedElement[] const rows = (await sheet.getRows()) as StringifiedElement[]
const elements: Element[] = [] const elements: Element[] = []
if (!rows[0]) { if (!rows[0]) {
// TODO: Report format error to database maintainers throw new Error(`No column types defined in sheet ${sheetName}`)
return []
} }
const types = _.pick(rows[0], Object.keys(specimen)) as Record<keyof Element, string> const types = _.pick(rows[0], Object.keys(specimen)) as Record<keyof Element, string>
rows.shift() rows.shift()
@ -47,7 +46,7 @@ export async function setList<Element extends object>(
// Load sheet into an array of objects // Load sheet into an array of objects
const rows = await sheet.getRows() const rows = await sheet.getRows()
if (!rows[0]) { if (!rows[0]) {
return undefined throw new Error(`No column types defined in sheet ${sheetName}`)
} }
const types = _.pick(rows[0], Object.keys(elements[0] || {})) as Record<keyof Element, string> const types = _.pick(rows[0], Object.keys(elements[0] || {})) as Record<keyof Element, string>
@ -58,10 +57,6 @@ export async function setList<Element extends object>(
const row = rows[rowid] const row = rows[rowid]
const stringifiedRow = stringifyElement(element, types) const stringifiedRow = stringifyElement(element, types)
if (stringifiedRow === undefined) {
return undefined
}
if (!row) { if (!row) {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
await sheet.addRow(stringifiedRow) await sheet.addRow(stringifiedRow)
@ -94,6 +89,39 @@ export async function setList<Element extends object>(
return true return true
} }
// eslint-disable-next-line @typescript-eslint/ban-types
export async function add<ElementNoId extends object, Element extends ElementNoId>(
sheetName: string,
idFieldName: string,
partialElement: Partial<ElementNoId>
): Promise<Element | undefined> {
if (!partialElement) {
return undefined
}
const sheet = await getGSheet(sheetName)
// Load sheet into an array of objects
const rows = await sheet.getRows()
if (!rows[0]) {
throw new Error(`No column types defined in sheet ${sheetName}`)
}
const types = {
[idFieldName]: "number",
...(_.pick(rows[0], Object.keys(partialElement || {})) as Record<keyof Element, string>),
}
// Create full element
rows.shift()
const highestId = rows.reduce((id: number, row) => Math.max(id, +row[idFieldName] || 0), 0)
const element = { [idFieldName]: highestId + 1, ...partialElement } as Element
// Add element
const stringifiedRow = stringifyElement(element, types)
await sheet.addRow(stringifiedRow)
return element
}
async function getGSheet(sheetName: string): Promise<GoogleSpreadsheetWorksheet> { async function getGSheet(sheetName: string): Promise<GoogleSpreadsheetWorksheet> {
const doc = new GoogleSpreadsheet("1pMMKcYx6NXLOqNn6pLHJTPMTOLRYZmSNg2QQcAu7-Pw") const doc = new GoogleSpreadsheet("1pMMKcYx6NXLOqNn6pLHJTPMTOLRYZmSNg2QQcAu7-Pw")
const creds = await fs.readFile(CRED_PATH) const creds = await fs.readFile(CRED_PATH)
@ -108,13 +136,10 @@ function parseElement<Element extends object>(
rawElement: Record<keyof Element, string>, rawElement: Record<keyof Element, string>,
types: Record<keyof Element, string>, types: Record<keyof Element, string>,
specimen: Element specimen: Element
): Element | undefined { ): Element {
const fullElement = _.reduce( const fullElement = _.reduce(
types, types,
(element: any, type: string, prop: string) => { (element: any, type: string, prop: string) => {
if (element === undefined) {
return undefined
}
const rawProp: string = rawElement[prop as keyof Element] const rawProp: string = rawElement[prop as keyof Element]
switch (type) { switch (type) {
case "string": case "string":
@ -136,14 +161,13 @@ function parseElement<Element extends object>(
element[prop] = new Date(+matchDate[3], +matchDate[2] - 1, +matchDate[1]) element[prop] = new Date(+matchDate[3], +matchDate[2] - 1, +matchDate[1])
break break
} }
return undefined // TODO: Report format error to database maintainers throw new Error(`Unable to read date from ${rawProp}`)
break
default: default:
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
const matchArrayType = type.match(/^(number|string|boolean|date)\[([^\]]+)\]$/) const matchArrayType = type.match(/^(number|string|boolean|date)\[([^\]]+)\]$/)
if (!matchArrayType) { if (!matchArrayType) {
return undefined throw new Error(`Unknown array type for ${type}`)
} }
if (!rawProp) { if (!rawProp) {
element[prop] = [] element[prop] = []
@ -189,10 +213,11 @@ function parseElement<Element extends object>(
return true return true
}) })
if (!rightFormat) { if (!rightFormat) {
return undefined throw new Error(`One array item is not a date in ${rawProp}`)
} }
break break
default: default:
throw new Error(`Unknown array type ${arrayType}`)
} }
} }
} }
@ -207,17 +232,10 @@ function parseElement<Element extends object>(
function stringifyElement<Element extends object>( function stringifyElement<Element extends object>(
element: Element, element: Element,
types: Record<keyof Element, string> types: Record<keyof Element, string>
): Record<keyof Element, string> | undefined { ): Record<keyof Element, string> {
const rawElement: Record<keyof Element, string> | undefined = _.reduce( const rawElement: Record<keyof Element, string> = _.reduce(
types, types,
( (stringifiedElement: Record<keyof Element, string>, type: string, prop: string) => {
stringifiedElement: Record<keyof Element, string> | undefined,
type: string,
prop: string
) => {
if (stringifiedElement === undefined) {
return undefined
}
const value = element[prop as keyof Element] const value = element[prop as keyof Element]
switch (type) { switch (type) {
case "string": case "string":
@ -233,22 +251,16 @@ function stringifyElement<Element extends object>(
break break
case "date": case "date":
if (value instanceof Date) { stringifiedElement[prop as keyof Element] = stringifiedDate(value)
stringifiedElement[prop as keyof Element] = `${value.getDate()}/${
value.getMonth() + 1
}/${value.getFullYear()}`
break break
} else {
console.error("Wrong date format in stringifyElement")
return undefined // TODO: Report format error to database maintainers
}
default: default:
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
const matchArrayType = type.match(/^(number|string|boolean|date)\[([^\]]+)\]$/) const matchArrayType = type.match(/^(number|string|boolean|date)\[([^\]]+)\]$/)
if (!matchArrayType || !_.isArray(value)) { if (!matchArrayType || !_.isArray(value)) {
console.error("Unknown matchArrayType or not an array in stringifyElement") throw new Error(
return undefined "Unknown matchArrayType or not an array in stringifyElement"
)
} }
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
const arrayType = matchArrayType[1] const arrayType = matchArrayType[1]
@ -258,7 +270,7 @@ function stringifyElement<Element extends object>(
switch (arrayType) { switch (arrayType) {
case "string": case "string":
if (!_.every(value, _.isString)) { if (!_.every(value, _.isString)) {
return undefined throw new Error(`Each date of ${value} is not a string`)
} }
stringifiedElement[prop as keyof Element] = formulaSafe( stringifiedElement[prop as keyof Element] = formulaSafe(
value.join(delimiter) value.join(delimiter)
@ -267,14 +279,14 @@ function stringifyElement<Element extends object>(
case "number": case "number":
if (!_.every(value, _.isNumber)) { if (!_.every(value, _.isNumber)) {
return undefined throw new Error(`Each date of ${value} is not a number`)
} }
stringifiedElement[prop as keyof Element] = value.join(delimiter) stringifiedElement[prop as keyof Element] = value.join(delimiter)
break break
case "boolean": case "boolean":
if (!_.every(value, _.isBoolean)) { if (!_.every(value, _.isBoolean)) {
return undefined throw new Error(`Each date of ${value} is not a boolean`)
} }
stringifiedElement[prop as keyof Element] = _.map(value, (val) => stringifiedElement[prop as keyof Element] = _.map(value, (val) =>
val ? "X" : "" val ? "X" : ""
@ -283,7 +295,7 @@ function stringifyElement<Element extends object>(
case "date": case "date":
if (!_.every(value, _.isDate)) { if (!_.every(value, _.isDate)) {
return undefined throw new Error(`Each date of ${value} is not a date`)
} }
stringifiedElement[prop as keyof Element] = _.map( stringifiedElement[prop as keyof Element] = _.map(
value, value,
@ -293,7 +305,7 @@ function stringifyElement<Element extends object>(
break break
default: default:
return undefined throw new Error(`Unknown array type ${arrayType}`)
} }
} }
@ -309,4 +321,19 @@ function formulaSafe(value: string): string {
return value.replace(/^=+/, "") return value.replace(/^=+/, "")
} }
function stringifiedDate(value: unknown): string {
let date: Date
if (value instanceof Date) {
date = value
} else if (typeof value === "string") {
try {
date = new Date(value)
} catch (e) {
throw new Error("Wrong date string format in stringifyElement")
}
} else {
throw new Error("Wrong date format in stringifyElement")
}
return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`
}
export { SCOPES } export { SCOPES }

View File

@ -6,7 +6,7 @@ import { Helmet } from "react-helmet"
import { AppState, AppThunk } from "../../store" import { AppState, AppThunk } from "../../store"
import { fetchJeuxJavListIfNeed } from "../../store/jeuxJavList" import { fetchJeuxJavListIfNeed } from "../../store/jeuxJavList"
import { fetchEnvieListIfNeed } from "../../store/envieList" import { fetchEnvieListIfNeed } from "../../store/envieList"
import { JeuxJavList } from "../../components" import { JeuxJavList, AddEnvie } from "../../components"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
export type Props = RouteComponentProps export type Props = RouteComponentProps
@ -31,9 +31,12 @@ function useList(stateToProp: (state: AppState) => any, fetchDataIfNeed: () => A
} }
} }
const Home: FC<Props> = (): JSX.Element => ( const Home: FC<Props> = (): JSX.Element => {
const dispatch = useDispatch()
return (
<div className={styles.Home}> <div className={styles.Home}>
<Helmet title="Home" /> <Helmet title="Home" />
<AddEnvie dispatch={dispatch} />
{/* {useList((state: AppState) => state.envieList, fetchEnvieListifNeed)()} */} {/* {useList((state: AppState) => state.envieList, fetchEnvieListifNeed)()} */}
{useList((state: AppState) => state.jeuxJavList, fetchJeuxJavListIfNeed)()} {useList((state: AppState) => state.jeuxJavList, fetchJeuxJavListIfNeed)()}
{/* <button type="button" onClick={() => setList([{id: 3, joueurs: 4, duree: 5, description: "abcd"}])}> {/* <button type="button" onClick={() => setList([{id: 3, joueurs: 4, duree: 5, description: "abcd"}])}>
@ -41,6 +44,7 @@ const Home: FC<Props> = (): JSX.Element => (
</button> */} </button> */}
</div> </div>
) )
}
// Fetch server-side data here // Fetch server-side data here
export const loadData = (): AppThunk[] => [ export const loadData = (): AppThunk[] => [

View File

@ -4,6 +4,71 @@ exports[`<Home /> renders an error if loading failed 1`] = `
<div <div
class="Home" class="Home"
> >
<section
class="EnvieList"
>
<h2>
Ajouter une nouvelle envie
</h2>
<form>
<label
for="postDomaine"
>
Domaine:
<input
id="postDomaine"
name="postDomaine"
type="text"
value=""
/>
</label>
<label
for="postEnvies"
>
Envies:
<textarea
id="postEnvies"
name="postEnvies"
/>
</label>
<label
for="postPrecisions"
>
Precisions:
<textarea
id="postPrecisions"
name="postPrecisions"
/>
</label>
<label
for="postEquipes"
>
Equipes:
<input
id="postEquipes"
name="postEquipes"
type="text"
value=""
/>
</label>
<label
for="postDateAjout"
>
DateAjout:
<input
id="postDateAjout"
name="postDateAjout"
type="date"
value=""
/>
</label>
<button
type="button"
>
Save Post
</button>
</form>
</section>
<p> <p>
Oops, Failed to load list! Oops, Failed to load list!
</p> </p>
@ -14,6 +79,71 @@ exports[`<Home /> renders the <List /> if loading was successful 1`] = `
<div <div
class="Home" class="Home"
> >
<section
class="EnvieList"
>
<h2>
Ajouter une nouvelle envie
</h2>
<form>
<label
for="postDomaine"
>
Domaine:
<input
id="postDomaine"
name="postDomaine"
type="text"
value=""
/>
</label>
<label
for="postEnvies"
>
Envies:
<textarea
id="postEnvies"
name="postEnvies"
/>
</label>
<label
for="postPrecisions"
>
Precisions:
<textarea
id="postPrecisions"
name="postPrecisions"
/>
</label>
<label
for="postEquipes"
>
Equipes:
<input
id="postEquipes"
name="postEquipes"
type="text"
value=""
/>
</label>
<label
for="postDateAjout"
>
DateAjout:
<input
id="postDateAjout"
name="postDateAjout"
type="date"
value=""
/>
</label>
<button
type="button"
>
Save Post
</button>
</form>
</section>
<div <div
class="JeuxJavList" class="JeuxJavList"
> >
@ -36,6 +166,71 @@ exports[`<Home /> renders the loading status if data invalid 1`] = `
<div <div
class="Home" class="Home"
> >
<section
class="EnvieList"
>
<h2>
Ajouter une nouvelle envie
</h2>
<form>
<label
for="postDomaine"
>
Domaine:
<input
id="postDomaine"
name="postDomaine"
type="text"
value=""
/>
</label>
<label
for="postEnvies"
>
Envies:
<textarea
id="postEnvies"
name="postEnvies"
/>
</label>
<label
for="postPrecisions"
>
Precisions:
<textarea
id="postPrecisions"
name="postPrecisions"
/>
</label>
<label
for="postEquipes"
>
Equipes:
<input
id="postEquipes"
name="postEquipes"
type="text"
value=""
/>
</label>
<label
for="postDateAjout"
>
DateAjout:
<input
id="postDateAjout"
name="postDateAjout"
type="date"
value=""
/>
</label>
<button
type="button"
>
Save Post
</button>
</form>
</section>
<p> <p>
Loading... Loading...
</p> </p>
@ -46,6 +241,71 @@ exports[`<Home /> renders the loading status if requesting data 1`] = `
<div <div
class="Home" class="Home"
> >
<section
class="EnvieList"
>
<h2>
Ajouter une nouvelle envie
</h2>
<form>
<label
for="postDomaine"
>
Domaine:
<input
id="postDomaine"
name="postDomaine"
type="text"
value=""
/>
</label>
<label
for="postEnvies"
>
Envies:
<textarea
id="postEnvies"
name="postEnvies"
/>
</label>
<label
for="postPrecisions"
>
Precisions:
<textarea
id="postPrecisions"
name="postPrecisions"
/>
</label>
<label
for="postEquipes"
>
Equipes:
<input
id="postEquipes"
name="postEquipes"
type="text"
value=""
/>
</label>
<label
for="postDateAjout"
>
DateAjout:
<input
id="postDateAjout"
name="postDateAjout"
type="date"
value=""
/>
</label>
<button
type="button"
>
Save Post
</button>
</form>
</section>
<p> <p>
Loading... Loading...
</p> </p>

View File

@ -11,7 +11,7 @@ import devServer from "./devServer"
import ssr from "./ssr" import ssr from "./ssr"
import { getJeuxJavList } from "../gsheets/jeuxJav" import { getJeuxJavList } from "../gsheets/jeuxJav"
import { getEnvieList, setEnvieList } from "../gsheets/envies" import { getEnvieList, addEnvie } from "../gsheets/envies"
import config from "../config" import config from "../config"
const app = express() const app = express()
@ -32,9 +32,10 @@ app.use(express.static(path.resolve(process.cwd(), "public")))
if (__DEV__) devServer(app) if (__DEV__) devServer(app)
// Google Sheets requests // Google Sheets requests
app.use(express.json())
app.get("/JeuxJav", getJeuxJavList) app.get("/JeuxJav", getJeuxJavList)
app.get("/GetList", getEnvieList) app.get("/GetEnvieList", getEnvieList)
app.post("/SetList", setEnvieList) app.post("/AddEnvie", addEnvie)
// Use React server-side rendering middleware // Use React server-side rendering middleware
app.get("*", ssr) app.get("*", ssr)

View File

@ -3,32 +3,40 @@ import axios from "axios"
import config from "../config" import config from "../config"
export class Envie { export class Envie {
envieId = 0
domaine = "" domaine = ""
envies = "" envies = ""
precisions = "" precisions = ""
equipes = [] equipes: string[] = []
dateAjout = new Date(0) dateAjout = ""
} }
export type EnvieWithoutId = Omit<Envie, "envieId">
export interface EnviesResponse { export interface GetEnvieListResponse {
data?: Envie[] data?: Envie[]
error?: Error error?: Error
} }
export const getEnvieList = async (): Promise<EnviesResponse> => { export const getEnvieList = async (): Promise<GetEnvieListResponse> => {
try { try {
const { data } = await axios.get(`${config.API_URL}/GetList`) const { data } = await axios.get(`${config.API_URL}/GetEnvieList`)
return { data } return { data }
} catch (error) { } catch (error) {
return { error: error as Error } return { error: error as Error }
} }
} }
export const setEnvieList = async (list: Envie[]): Promise<EnviesResponse> => {
export interface AddEnvieResponse {
data?: Envie
error?: Error
}
export const addEnvie = async (envieWithoutId: EnvieWithoutId): Promise<AddEnvieResponse> => {
try { try {
const { data } = await axios.post(`${config.API_URL}/SetList`, list) const { data } = await axios.post(`${config.API_URL}/AddEnvie`, envieWithoutId)
return { data } return { data }
} catch (error) { } catch (error) {
return { error: error as Error } return { error: error as Error }

70
src/store/envieAdd.ts Normal file
View File

@ -0,0 +1,70 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit"
import { toast } from "react-toastify"
import { Envie, EnvieWithoutId, addEnvie } from "../services/envies"
import { AppThunk } from "."
interface EnvieRequest {
readyStatus: string
items: Envie | null
error: string | null
}
export const initialState: EnvieRequest = {
readyStatus: "invalid",
items: null,
error: null,
}
const envieList = createSlice({
name: "addEnvie",
initialState,
reducers: {
getRequesting: (state: EnvieRequest) => {
state.readyStatus = "request"
},
getSuccess: (state, { payload }: PayloadAction<Envie>) => {
state.readyStatus = "success"
state.items = payload
},
getFailure: (state, { payload }: PayloadAction<string>) => {
state.readyStatus = "failure"
state.error = payload
},
},
})
export default envieList.reducer
export const { getRequesting, getSuccess, getFailure } = envieList.actions
export const postEnvie =
(envieWithoutId: EnvieWithoutId): AppThunk =>
async (dispatch) => {
dispatch(getRequesting())
const { error, data } = await addEnvie(envieWithoutId)
if (error) {
dispatch(getFailure(error.message))
toast.error(`Erreur lors de l'ajout: ${error.message}`, {
position: "top-center",
autoClose: 6000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
})
} else {
dispatch(getSuccess(data as Envie))
toast.success("Envie ajoutée !", {
position: "top-center",
autoClose: 3000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
})
}
}

View File

@ -3,23 +3,23 @@ import { PayloadAction, createSlice } from "@reduxjs/toolkit"
import { Envie, getEnvieList } from "../services/envies" import { Envie, getEnvieList } from "../services/envies"
import { AppThunk, AppState } from "." import { AppThunk, AppState } from "."
interface EnvieList { interface EnvieListRequest {
readyStatus: string readyStatus: string
items: Envie[] items: Envie[]
error: string | null error: string | null
} }
export const initialState: EnvieList = { export const initialState: EnvieListRequest = {
readyStatus: "invalid", readyStatus: "invalid",
items: [], items: [],
error: null, error: null,
} }
const envieList = createSlice({ const envieList = createSlice({
name: "envieList", name: "getEnvieList",
initialState, initialState,
reducers: { reducers: {
getRequesting: (state: EnvieList) => { getRequesting: (state: EnvieListRequest) => {
state.readyStatus = "request" state.readyStatus = "request"
}, },
getSuccess: (state, { payload }: PayloadAction<Envie[]>) => { getSuccess: (state, { payload }: PayloadAction<Envie[]>) => {

View File

@ -4,7 +4,7 @@ import { JeuxJav, getJeuxJavList } from "../services/jeuxJav"
import { AppThunk, AppState } from "." import { AppThunk, AppState } from "."
interface JeuxJavList { interface JeuxJavList {
readyStatus: string readyStatus: string // TODO Change it to: "invalid" | "request" | "success" | "failure"
items: JeuxJav[] items: JeuxJav[]
error: string | null error: string | null
} }

2160
yarn.lock

File diff suppressed because it is too large Load Diff