Merge branch 'feature/user-profile' of Cadoles/daddy into develop
This commit is contained in:
198
client/package-lock.json
generated
198
client/package-lock.json
generated
@ -1230,8 +1230,7 @@
|
||||
"@types/node": {
|
||||
"version": "13.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.12.tgz",
|
||||
"integrity": "sha512-zWz/8NEPxoXNT9YyF2osqyA9WjssZukYpgI4UYZpOjcyqwIUqWGkcCionaEb9Ki+FULyPyvNFpg/329Kd2/pbw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-zWz/8NEPxoXNT9YyF2osqyA9WjssZukYpgI4UYZpOjcyqwIUqWGkcCionaEb9Ki+FULyPyvNFpg/329Kd2/pbw=="
|
||||
},
|
||||
"@types/prop-types": {
|
||||
"version": "15.7.3",
|
||||
@ -1372,6 +1371,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/zen-observable": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz",
|
||||
"integrity": "sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg=="
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
|
||||
@ -1547,6 +1551,23 @@
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"@wry/context": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.4.4.tgz",
|
||||
"integrity": "sha512-LrKVLove/zw6h2Md/KZyWxIkFM6AoyKp71OqpH9Hiip1csjPVoD3tPxlbQUNxEnHENks3UGgNpSBCAfq9KWuag==",
|
||||
"requires": {
|
||||
"@types/node": ">=6",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@wry/equality": {
|
||||
"version": "0.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.1.11.tgz",
|
||||
"integrity": "sha512-mwEVBDUVODlsQQ5dfuLUS5/Tf7jqUKyhKYHmVi4fPB6bDMOfWvUPJmKgS1Z7Za/sOI3vzWt4+O7yCiL/70MogA==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
@ -1723,6 +1744,93 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"apollo-cache": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.3.5.tgz",
|
||||
"integrity": "sha512-1XoDy8kJnyWY/i/+gLTEbYLnoiVtS8y7ikBr/IfmML4Qb+CM7dEEbIUOjnY716WqmZ/UpXIxTfJsY7rMcqiCXA==",
|
||||
"requires": {
|
||||
"apollo-utilities": "^1.3.4",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"apollo-cache-inmemory": {
|
||||
"version": "1.6.6",
|
||||
"resolved": "https://registry.npmjs.org/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.6.tgz",
|
||||
"integrity": "sha512-L8pToTW/+Xru2FFAhkZ1OA9q4V4nuvfoPecBM34DecAugUZEBhI2Hmpgnzq2hTKZ60LAMrlqiASm0aqAY6F8/A==",
|
||||
"requires": {
|
||||
"apollo-cache": "^1.3.5",
|
||||
"apollo-utilities": "^1.3.4",
|
||||
"optimism": "^0.10.0",
|
||||
"ts-invariant": "^0.4.0",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"apollo-client": {
|
||||
"version": "2.6.10",
|
||||
"resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.6.10.tgz",
|
||||
"integrity": "sha512-jiPlMTN6/5CjZpJOkGeUV0mb4zxx33uXWdj/xQCfAMkuNAC3HN7CvYDyMHHEzmcQ5GV12LszWoQ/VlxET24CtA==",
|
||||
"requires": {
|
||||
"@types/zen-observable": "^0.8.0",
|
||||
"apollo-cache": "1.3.5",
|
||||
"apollo-link": "^1.0.0",
|
||||
"apollo-utilities": "1.3.4",
|
||||
"symbol-observable": "^1.0.2",
|
||||
"ts-invariant": "^0.4.0",
|
||||
"tslib": "^1.10.0",
|
||||
"zen-observable": "^0.8.0"
|
||||
}
|
||||
},
|
||||
"apollo-link": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.14.tgz",
|
||||
"integrity": "sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg==",
|
||||
"requires": {
|
||||
"apollo-utilities": "^1.3.0",
|
||||
"ts-invariant": "^0.4.0",
|
||||
"tslib": "^1.9.3",
|
||||
"zen-observable-ts": "^0.8.21"
|
||||
}
|
||||
},
|
||||
"apollo-link-http": {
|
||||
"version": "1.5.17",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.17.tgz",
|
||||
"integrity": "sha512-uWcqAotbwDEU/9+Dm9e1/clO7hTB2kQ/94JYcGouBVLjoKmTeJTUPQKcJGpPwUjZcSqgYicbFqQSoJIW0yrFvg==",
|
||||
"requires": {
|
||||
"apollo-link": "^1.2.14",
|
||||
"apollo-link-http-common": "^0.2.16",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"apollo-link-http-common": {
|
||||
"version": "0.2.16",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.16.tgz",
|
||||
"integrity": "sha512-2tIhOIrnaF4UbQHf7kjeQA/EmSorB7+HyJIIrUjJOKBgnXwuexi8aMecRlqTIDWcyVXCeqLhUnztMa6bOH/jTg==",
|
||||
"requires": {
|
||||
"apollo-link": "^1.2.14",
|
||||
"ts-invariant": "^0.4.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"apollo-link-ws": {
|
||||
"version": "1.0.20",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link-ws/-/apollo-link-ws-1.0.20.tgz",
|
||||
"integrity": "sha512-mjSFPlQxmoLArpHBeUb2Xj+2HDYeTaJqFGOqQ+I8NVJxgL9lJe84PDWcPah/yMLv3rB7QgBDSuZ0xoRFBPlySw==",
|
||||
"requires": {
|
||||
"apollo-link": "^1.2.14",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"apollo-utilities": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.3.4.tgz",
|
||||
"integrity": "sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig==",
|
||||
"requires": {
|
||||
"@wry/equality": "^0.1.2",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"ts-invariant": "^0.4.0",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
|
||||
@ -1875,8 +1983,7 @@
|
||||
"async-limiter": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
|
||||
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
@ -2724,6 +2831,11 @@
|
||||
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
|
||||
"dev": true
|
||||
},
|
||||
"backo2": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
|
||||
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
@ -5029,8 +5141,7 @@
|
||||
"fast-json-stable-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
|
||||
},
|
||||
"fastparse": {
|
||||
"version": "1.1.2",
|
||||
@ -5499,11 +5610,21 @@
|
||||
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
|
||||
"dev": true
|
||||
},
|
||||
"graphql": {
|
||||
"version": "15.3.0",
|
||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-15.3.0.tgz",
|
||||
"integrity": "sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w=="
|
||||
},
|
||||
"graphql-request": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-2.0.0.tgz",
|
||||
"integrity": "sha512-Ww3Ax+G3l2d+mPT8w7HC9LfrKjutnCKtnDq7ZZp2ghVk5IQDjwAk3/arRF1ix17Ky15rm0hrSKVKxRhIVlSuoQ=="
|
||||
},
|
||||
"graphql-tag": {
|
||||
"version": "2.10.4",
|
||||
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.4.tgz",
|
||||
"integrity": "sha512-O7vG5BT3w6Sotc26ybcvLKNTdfr4GfsIVMD+LdYqXCeJIYPRyp8BIsDOUtxw7S1PYvRw5vH3278J2EDezR6mfA=="
|
||||
},
|
||||
"handle-thing": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
|
||||
@ -6308,6 +6429,11 @@
|
||||
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
|
||||
"dev": true
|
||||
},
|
||||
"iterall": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz",
|
||||
"integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg=="
|
||||
},
|
||||
"js-base64": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz",
|
||||
@ -7340,6 +7466,14 @@
|
||||
"is-wsl": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"optimism": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.10.3.tgz",
|
||||
"integrity": "sha512-9A5pqGoQk49H6Vhjb9kPgAeeECfUDF6aIICbMDL23kDLStBn1MWk3YvcZ4xWF9CsSf6XEgvRLkXy4xof/56vVw==",
|
||||
"requires": {
|
||||
"@wry/context": "^0.4.0"
|
||||
}
|
||||
},
|
||||
"original": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
|
||||
@ -9420,6 +9554,33 @@
|
||||
"resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz",
|
||||
"integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw=="
|
||||
},
|
||||
"subscriptions-transport-ws": {
|
||||
"version": "0.9.17",
|
||||
"resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.17.tgz",
|
||||
"integrity": "sha512-hNHi2N80PBz4T0V0QhnnsMGvG3XDFDS9mS6BhZ3R12T6EBywC8d/uJscsga0cVO4DKtXCkCRrWm2sOYrbOdhEA==",
|
||||
"requires": {
|
||||
"backo2": "^1.0.2",
|
||||
"eventemitter3": "^3.1.0",
|
||||
"iterall": "^1.2.1",
|
||||
"symbol-observable": "^1.0.4",
|
||||
"ws": "^5.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"eventemitter3": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
|
||||
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
|
||||
},
|
||||
"ws": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
|
||||
"integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
|
||||
"requires": {
|
||||
"async-limiter": "~1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
@ -9658,6 +9819,14 @@
|
||||
"glob": "^7.1.2"
|
||||
}
|
||||
},
|
||||
"ts-invariant": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.4.tgz",
|
||||
"integrity": "sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"ts-loader": {
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-7.0.5.tgz",
|
||||
@ -9725,8 +9894,7 @@
|
||||
"tslib": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
|
||||
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
|
||||
},
|
||||
"tty-browserify": {
|
||||
"version": "0.0.0",
|
||||
@ -10833,6 +11001,20 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"zen-observable": {
|
||||
"version": "0.8.15",
|
||||
"resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz",
|
||||
"integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="
|
||||
},
|
||||
"zen-observable-ts": {
|
||||
"version": "0.8.21",
|
||||
"resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz",
|
||||
"integrity": "sha512-Yj3yXweRc8LdRMrCC8nIc4kkjWecPAUVh0TI0OUrWXx6aX790vLcDlWca6I4vsyCGH3LpWxq0dJRcMOFoVqmeg==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.3",
|
||||
"zen-observable": "^0.8.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,9 +52,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/qs": "^6.9.3",
|
||||
"apollo-cache-inmemory": "^1.6.6",
|
||||
"apollo-client": "^2.6.10",
|
||||
"apollo-link": "^1.2.14",
|
||||
"apollo-link-http": "^1.5.17",
|
||||
"apollo-link-ws": "^1.0.20",
|
||||
"apollo-utilities": "^1.3.4",
|
||||
"bulma": "^0.7.2",
|
||||
"bulma-switch": "^2.0.0",
|
||||
"graphql": "^15.3.0",
|
||||
"graphql-request": "^2.0.0",
|
||||
"graphql-tag": "^2.10.4",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"qs": "^6.9.4",
|
||||
"react": "^16.12.0",
|
||||
@ -65,6 +73,7 @@
|
||||
"redux": "^4.0.4",
|
||||
"redux-saga": "^1.1.3",
|
||||
"styled-components": "^4.4.1",
|
||||
"subscriptions-transport-ws": "^0.9.17",
|
||||
"typescript": "^3.8.3"
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { BrowserRouter, Route, Redirect, Switch } from "react-router-dom";
|
||||
import { HomePage } from './HomePage/HomePage';
|
||||
import { store } from '../store/store';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ProfilePage } from './ProfilePage/ProfilePage';
|
||||
|
||||
export class App extends React.Component {
|
||||
render() {
|
||||
@ -11,6 +12,7 @@ export class App extends React.Component {
|
||||
<BrowserRouter>
|
||||
<Switch>
|
||||
<Route path="/" exact component={HomePage} />
|
||||
<Route path="/profile" exact component={ProfilePage} />
|
||||
<Route component={() => <Redirect to="/" />} />
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
|
@ -7,15 +7,15 @@ export function HomePage() {
|
||||
const currentUser = useSelector((state: RootState) => state.auth.currentUser);
|
||||
|
||||
return (
|
||||
<Page title="Daddy - Accueil">
|
||||
<Page title="Accueil">
|
||||
<div className="container is-fluid">
|
||||
<section className="section">
|
||||
<div className="columns">
|
||||
<div className="column is-4 is-offset-4">
|
||||
<div className="box">
|
||||
{
|
||||
currentUser && currentUser.full_name ?
|
||||
<p>Bonjour <span className="has-text-weight-bold">{currentUser.full_name}</span> !</p> :
|
||||
currentUser && currentUser.email ?
|
||||
<p>Bonjour <span className="has-text-weight-bold">{currentUser.name ? currentUser.name : currentUser.email}</span> !</p> :
|
||||
<p>Veuillez vous authentifier.</p>
|
||||
}
|
||||
</div>
|
||||
|
@ -1,44 +1,62 @@
|
||||
import React from 'react';
|
||||
import React, { Fragment, useState } from 'react';
|
||||
import logo from '../resources/logo.svg';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '../store/reducers/root';
|
||||
import { Config } from '../config';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export function Navbar() {
|
||||
const isAuthenticated = useSelector<RootState>(state => state.auth.isAuthenticated);
|
||||
|
||||
const [ isActive, setActive ] = useState(false);
|
||||
|
||||
const toggleMenu = () => {
|
||||
setActive(active => !active);
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="navbar" role="navigation" aria-label="main navigation">
|
||||
<div className="container is-fluid">
|
||||
<div className="navbar-brand">
|
||||
<a className="navbar-item" href="#/">
|
||||
<Link className="navbar-item" to="/">
|
||||
<img src={logo} style={{marginRight:'5px',width:'28px',height:'28px'}} />
|
||||
<h1 className="is-size-4">Daddy</h1>
|
||||
</a>
|
||||
<a role="button" className="navbar-burger" aria-label="menu" aria-expanded="false">
|
||||
</Link>
|
||||
<a role="button"
|
||||
className={`navbar-burger ${isActive ? 'is-active' : ''}`}
|
||||
onClick={toggleMenu}
|
||||
aria-label="menu"
|
||||
aria-expanded="false">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="navbar-menu">
|
||||
<div className={`navbar-menu ${isActive ? 'is-active' : ''}`}>
|
||||
<div className="navbar-end">
|
||||
<div className="navbar-item">
|
||||
<div className="buttons">
|
||||
{
|
||||
isAuthenticated ?
|
||||
<a className="button is-small" href={Config.logoutURL}>
|
||||
<span className="icon">
|
||||
<i className="fas fa-sign-out-alt"></i>
|
||||
</span>
|
||||
<span>Se déconnecter</span>
|
||||
</a> :
|
||||
<a className="button is-small" href={Config.loginURL}>
|
||||
<Fragment>
|
||||
<Link to="/profile" className="button">
|
||||
<span className="icon">
|
||||
<i className="fas fa-user"></i>
|
||||
</span>
|
||||
</Link>
|
||||
<a className="button" href={Config.logoutURL}>
|
||||
<span className="icon">
|
||||
<i className="fas fa-sign-out-alt"></i>
|
||||
</span>
|
||||
</a>
|
||||
</Fragment> :
|
||||
<a className="button" href={Config.loginURL}>
|
||||
<span className="icon">
|
||||
<i className="fas fa-sign-in-alt"></i>
|
||||
</span>
|
||||
<span>Se connecter</span>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -25,6 +25,6 @@ export class Page extends React.PureComponent<PageProps> {
|
||||
|
||||
updateTitle() {
|
||||
const { title } = this.props;
|
||||
if (title !== undefined) window.document.title = title;
|
||||
if (title !== undefined) window.document.title = title + ' - Daddy';
|
||||
}
|
||||
}
|
38
client/src/components/ProfilePage/ProfilePage.tsx
Normal file
38
client/src/components/ProfilePage/ProfilePage.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Page } from '../Page';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { RootState } from '../../store/reducers/root';
|
||||
import { UserForm } from '../UserForm';
|
||||
import { Loader } from '../Loader';
|
||||
import { User } from '../../types/user';
|
||||
import { updateProfile } from '../../store/actions/profile';
|
||||
|
||||
export function ProfilePage() {
|
||||
const currentUser = useSelector((state: RootState) => state.auth.currentUser);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onUserChange = (user: User) => {
|
||||
if (currentUser.name !== user.name) {
|
||||
dispatch(updateProfile({ name: user.name }))
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Page title="Mon profil">
|
||||
<div className="container is-fluid">
|
||||
<section className="section">
|
||||
<div className="columns">
|
||||
<div className="column is-6 is-offset-3">
|
||||
<h2 className="is-size-2 subtitle">Mon profil</h2>
|
||||
{
|
||||
currentUser ?
|
||||
<UserForm onChange={onUserChange} user={currentUser} /> :
|
||||
<Loader />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
80
client/src/components/UserForm.tsx
Normal file
80
client/src/components/UserForm.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import React, { useState, ChangeEvent, useEffect } from 'react';
|
||||
import { User } from '../types/user';
|
||||
|
||||
export interface UserFormProps {
|
||||
user: User
|
||||
onChange?: (user: User) => void
|
||||
}
|
||||
|
||||
export function UserForm({ user, onChange }: UserFormProps) {
|
||||
const [ state, setState ] = useState({
|
||||
changed: false,
|
||||
user: {
|
||||
name: '',
|
||||
email: '',
|
||||
createdAt: null,
|
||||
connectedAt: null,
|
||||
...user,
|
||||
}
|
||||
});
|
||||
|
||||
const onSaveClick = () => {
|
||||
if (!state.changed) return;
|
||||
if (typeof onChange !== 'function') return;
|
||||
onChange(state.user);
|
||||
setState(state => {
|
||||
return {
|
||||
...state,
|
||||
changed: false,
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
const onUserAttrChange = function(attrName: string, evt: ChangeEvent<HTMLInputElement>) {
|
||||
const value = evt.currentTarget.value;
|
||||
setState(state => {
|
||||
return {
|
||||
...state,
|
||||
changed: true,
|
||||
user: {
|
||||
...state.user,
|
||||
[attrName]: value,
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form">
|
||||
<div className="field">
|
||||
<label className="label">Nom d'utilisateur</label>
|
||||
<div className="control">
|
||||
<input type="text" className="input" value={state.user.name}
|
||||
onChange={onUserAttrChange.bind(null, "name")} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label className="label">Adresse courriel</label>
|
||||
<div className="control">
|
||||
<p className="input is-static">{state.user.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label className="label">Date de dernière connexion</label>
|
||||
<div className="control">
|
||||
<p className="input is-static">{state.user.connectedAt}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label className="label">Date de création</label>
|
||||
<div className="control">
|
||||
<p className="input is-static">{state.user.createdAt}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="buttons is-right">
|
||||
<button disabled={!state.changed}
|
||||
className="button is-primary" onClick={onSaveClick}>Enregistrer</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -2,6 +2,7 @@ export const Config = {
|
||||
loginURL: get<string>("loginURL", "http://localhost:8081/login"),
|
||||
logoutURL: get<string>("logoutURL", "http://localhost:8081/logout"),
|
||||
graphQLEndpoint: get<string>("graphQLEndpoint", "http://localhost:8081/api/v1/graphql"),
|
||||
subscriptionEndpoint: get<string>("subscriptionEndpoint", "ws://localhost:8081/api/v1/graphql"),
|
||||
};
|
||||
|
||||
function get<T>(key: string, defaultValue: T):T {
|
||||
|
@ -16,4 +16,25 @@ export interface fetchProfileSuccessAction extends Action {
|
||||
|
||||
export function fetchProfile(): fetchProfileRequestAction {
|
||||
return { type: FETCH_PROFILE_REQUEST }
|
||||
}
|
||||
|
||||
export const UPDATE_PROFILE_REQUEST = 'UPDATE_PROFILE_REQUEST';
|
||||
export const UPDATE_PROFILE_SUCCESS = 'UPDATE_PROFILE_SUCCESS';
|
||||
export const UPDATE_PROFILE_FAILURE = 'UPDATE_PROFILE_FAILURE';
|
||||
|
||||
export interface ProfileChanges {
|
||||
name?: string
|
||||
}
|
||||
|
||||
export interface updateProfileRequestAction extends Action {
|
||||
changes: ProfileChanges
|
||||
}
|
||||
|
||||
export interface updateProfileSuccessAction extends Action {
|
||||
profile: User
|
||||
}
|
||||
|
||||
|
||||
export function updateProfile(changes: ProfileChanges): updateProfileRequestAction {
|
||||
return { type: UPDATE_PROFILE_REQUEST, changes }
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { Action } from "redux";
|
||||
import { User } from "../../types/user";
|
||||
import { SET_CURRENT_USER, setCurrentUserAction } from "../actions/auth";
|
||||
import { FETCH_PROFILE_SUCCESS, fetchProfileSuccessAction } from "../actions/profile";
|
||||
import { FETCH_PROFILE_SUCCESS, fetchProfileSuccessAction, updateProfileSuccessAction, UPDATE_PROFILE_SUCCESS, updateProfileRequestAction } from "../actions/profile";
|
||||
|
||||
export interface AuthState {
|
||||
isAuthenticated: boolean
|
||||
@ -19,6 +19,8 @@ export function authReducer(state = defaultState, action: Action): AuthState {
|
||||
return handleSetCurrentUser(state, action as setCurrentUserAction);
|
||||
case FETCH_PROFILE_SUCCESS:
|
||||
return handleFetchProfileSuccess(state, action as fetchProfileSuccessAction);
|
||||
case UPDATE_PROFILE_SUCCESS:
|
||||
return handleFetchProfileSuccess(state, action as updateProfileSuccessAction);
|
||||
|
||||
}
|
||||
return state;
|
||||
@ -42,4 +44,14 @@ function handleFetchProfileSuccess(state: AuthState, { profile }: fetchProfileSu
|
||||
...profile,
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function handleUpdateProfileSuccess(state: AuthState, { profile }: updateProfileSuccessAction): AuthState {
|
||||
return {
|
||||
...state,
|
||||
isAuthenticated: true,
|
||||
currentUser: {
|
||||
...profile,
|
||||
}
|
||||
};
|
||||
};
|
@ -1,7 +1,12 @@
|
||||
import { all, put } from "redux-saga/effects";
|
||||
import { fetchProfile } from "../actions/profile";
|
||||
|
||||
export function* initRootSaga() {
|
||||
yield all([
|
||||
|
||||
fetchUserProfileSaga(),
|
||||
]);
|
||||
}
|
||||
|
||||
export function* fetchUserProfileSaga() {
|
||||
yield put(fetchProfile());
|
||||
}
|
46
client/src/store/sagas/profile.ts
Normal file
46
client/src/store/sagas/profile.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { DaddyClient, getClient } from "../../util/daddy";
|
||||
import { Config } from "../../config";
|
||||
import { all, takeLatest, put, select } from "redux-saga/effects";
|
||||
import { FETCH_PROFILE_REQUEST, fetchProfile, FETCH_PROFILE_FAILURE, FETCH_PROFILE_SUCCESS, updateProfileRequestAction, UPDATE_PROFILE_REQUEST, UPDATE_PROFILE_FAILURE, UPDATE_PROFILE_SUCCESS } from "../actions/profile";
|
||||
import { SET_CURRENT_USER } from "../actions/auth";
|
||||
import { User } from "../../types/user";
|
||||
|
||||
export function* profileRootSaga() {
|
||||
yield all([
|
||||
takeLatest(SET_CURRENT_USER, onCurrentUserChangeSaga),
|
||||
takeLatest(FETCH_PROFILE_REQUEST, fetchProfileSaga),
|
||||
takeLatest(UPDATE_PROFILE_REQUEST, updateProfileSaga),
|
||||
]);
|
||||
}
|
||||
|
||||
export function* onCurrentUserChangeSaga() {
|
||||
yield put(fetchProfile());
|
||||
}
|
||||
|
||||
export function* fetchProfileSaga() {
|
||||
const client = getClient(Config.graphQLEndpoint, Config.subscriptionEndpoint);
|
||||
|
||||
let profile: User;
|
||||
try {
|
||||
profile = yield client.fetchProfile().then(result => result.userProfile);
|
||||
} catch(err) {
|
||||
yield put({ type: FETCH_PROFILE_FAILURE, err });
|
||||
return;
|
||||
}
|
||||
|
||||
yield put({type: FETCH_PROFILE_SUCCESS, profile });
|
||||
}
|
||||
|
||||
export function* updateProfileSaga({ changes }: updateProfileRequestAction) {
|
||||
const client = getClient(Config.graphQLEndpoint, Config.subscriptionEndpoint);
|
||||
|
||||
let profile: User;
|
||||
try {
|
||||
profile = yield client.updateProfile(changes).then(result => result.updateProfile);
|
||||
} catch(err) {
|
||||
yield put({ type: UPDATE_PROFILE_FAILURE, err });
|
||||
return;
|
||||
}
|
||||
|
||||
yield put({type: UPDATE_PROFILE_SUCCESS, profile });
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
import { all } from 'redux-saga/effects';
|
||||
import { failureRootSaga } from './failure';
|
||||
import { initRootSaga } from './init';
|
||||
import { usersRootSaga } from './users';
|
||||
import { profileRootSaga } from './profile';
|
||||
|
||||
export function* rootSaga() {
|
||||
yield all([
|
||||
initRootSaga(),
|
||||
failureRootSaga(),
|
||||
profileRootSaga(),
|
||||
]);
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
import { DaddyClient } from "../../util/daddy";
|
||||
import { Config } from "../../config";
|
||||
import { all, takeLatest, put, select } from "redux-saga/effects";
|
||||
import { FETCH_PROFILE_REQUEST, fetchProfile, FETCH_PROFILE_FAILURE, FETCH_PROFILE_SUCCESS } from "../actions/profile";
|
||||
import { SET_CURRENT_USER } from "../actions/auth";
|
||||
import { RootState } from "../reducers/root";
|
||||
import { User } from "../../types/user";
|
||||
|
||||
export function* usersRootSaga() {
|
||||
yield all([
|
||||
takeLatest(SET_CURRENT_USER, onCurrentUserChangeSaga),
|
||||
takeLatest(FETCH_PROFILE_REQUEST, fetchProfileSaga),
|
||||
]);
|
||||
}
|
||||
|
||||
export function* onCurrentUserChangeSaga() {
|
||||
yield put(fetchProfile());
|
||||
}
|
||||
|
||||
export function* fetchProfileSaga() {
|
||||
const client = new DaddyClient(Config.graphQLEndpoint);
|
||||
|
||||
let profile: User;
|
||||
try {
|
||||
const currentUser: User = yield select((state: RootState) => state.auth.currentUser);
|
||||
profile = yield client.fetchUser(currentUser.email).then(result => result.user);
|
||||
} catch(err) {
|
||||
yield put({ type: FETCH_PROFILE_FAILURE, err });
|
||||
return;
|
||||
}
|
||||
|
||||
yield put({type: FETCH_PROFILE_SUCCESS, profile });
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
export interface User {
|
||||
email: string
|
||||
full_name?: string
|
||||
updated_at?: Date
|
||||
created_at?: Date
|
||||
name?: string
|
||||
connectedAt?: Date
|
||||
createdAt?: Date
|
||||
}
|
@ -1,5 +1,11 @@
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { Config } from "../config";
|
||||
import ApolloClient, { DefaultOptions } from 'apollo-client';
|
||||
import { InMemoryCache } from 'apollo-cache-inmemory';
|
||||
import { split } from 'apollo-link';
|
||||
import { HttpLink } from 'apollo-link-http';
|
||||
import { WebSocketLink } from 'apollo-link-ws';
|
||||
import { getMainDefinition, variablesInOperation } from 'apollo-utilities';
|
||||
import gql from 'graphql-tag';
|
||||
import { ProfileChanges } from '../store/actions/profile';
|
||||
|
||||
export class UnauthorizedError extends Error {
|
||||
constructor(...args: any[]) {
|
||||
@ -8,30 +14,96 @@ export class UnauthorizedError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
let client: DaddyClient
|
||||
|
||||
export function getClient(graphQLEndpoint: string, subscriptionEndpoint: string): DaddyClient {
|
||||
if (!client) {
|
||||
client = new DaddyClient(graphQLEndpoint, subscriptionEndpoint);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export class DaddyClient {
|
||||
|
||||
gql: GraphQLClient
|
||||
gql: ApolloClient<InMemoryCache>
|
||||
|
||||
constructor(endpoint: string) {
|
||||
this.gql = new GraphQLClient(endpoint, {
|
||||
headers: {
|
||||
mode: 'cors',
|
||||
constructor(graphQLEndpoint: string, subscriptionEndpoint: string) {
|
||||
const wsLink = new WebSocketLink({
|
||||
uri: subscriptionEndpoint,
|
||||
options: {
|
||||
reconnect: true
|
||||
}
|
||||
});
|
||||
|
||||
const httpLink = new HttpLink({
|
||||
uri: graphQLEndpoint,
|
||||
fetchOptions: {
|
||||
mode: 'cors',
|
||||
credentials: 'include',
|
||||
}
|
||||
});
|
||||
|
||||
const link = split(
|
||||
({ query }) => {
|
||||
const definition = getMainDefinition(query);
|
||||
return (
|
||||
definition.kind === 'OperationDefinition' &&
|
||||
definition.operation === 'subscription'
|
||||
);
|
||||
},
|
||||
wsLink,
|
||||
httpLink,
|
||||
);
|
||||
|
||||
const defaultOptions: DefaultOptions = {
|
||||
watchQuery: {
|
||||
fetchPolicy: 'no-cache',
|
||||
errorPolicy: 'ignore',
|
||||
},
|
||||
query: {
|
||||
fetchPolicy: 'no-cache',
|
||||
errorPolicy: 'all',
|
||||
},
|
||||
};
|
||||
|
||||
this.gql = new ApolloClient<any>({
|
||||
link: link,
|
||||
cache: new InMemoryCache(),
|
||||
defaultOptions,
|
||||
});
|
||||
}
|
||||
|
||||
fetchUser(email: string) {
|
||||
return this.gql.rawRequest(`
|
||||
query fetchUser {
|
||||
user(where: {email: {eq: $email}}) {
|
||||
id
|
||||
created_at
|
||||
updated_at
|
||||
fetchProfile() {
|
||||
return this.gql.query({
|
||||
query: gql`
|
||||
query {
|
||||
userProfile {
|
||||
name,
|
||||
email,
|
||||
full_name
|
||||
createdAt,
|
||||
connectedAt
|
||||
}
|
||||
}
|
||||
`, { email })
|
||||
}`
|
||||
})
|
||||
.then(this.assertAuthorization)
|
||||
}
|
||||
|
||||
updateProfile(changes: ProfileChanges) {
|
||||
return this.gql.mutate({
|
||||
variables: {
|
||||
changes,
|
||||
},
|
||||
mutation: gql`
|
||||
mutation updateProfile($changes: ProfileChanges!) {
|
||||
updateProfile(changes: $changes) {
|
||||
name,
|
||||
email,
|
||||
createdAt,
|
||||
connectedAt
|
||||
}
|
||||
}`,
|
||||
})
|
||||
.then(this.assertAuthorization)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user