Remplacement de Redux/Saga par Apollo
This commit is contained in:
parent
8708e30020
commit
c4373cce46
|
@ -4,6 +4,43 @@
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@apollo/client": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-4ighan5Anlj4tK/tdUHs4Mi1njqXZ7AxRCVolz/H702DjPphAJfm+FRkIadPTmwz+OLO+d+tX+6V1VBshf02rg==",
|
||||||
|
"requires": {
|
||||||
|
"@types/zen-observable": "^0.8.0",
|
||||||
|
"@wry/context": "^0.5.2",
|
||||||
|
"@wry/equality": "^0.1.9",
|
||||||
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
|
"graphql-tag": "^2.10.4",
|
||||||
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
|
"optimism": "^0.12.1",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"symbol-observable": "^1.2.0",
|
||||||
|
"ts-invariant": "^0.4.4",
|
||||||
|
"tslib": "^1.10.0",
|
||||||
|
"zen-observable": "^0.8.14"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@wry/context": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-B/JLuRZ/vbEKHRUiGj6xiMojST1kHhu4WcreLfNN7q9DqQFrb97cWgf/kiYsPSUCAMVN0HzfFc8XjJdzgZzfjw==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.9.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimism": {
|
||||||
|
"version": "0.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.12.1.tgz",
|
||||||
|
"integrity": "sha512-t8I7HM1dw0SECitBYAqFOVHoBAHEQBTeKjIL9y9ImHzAVkdyPK4ifTgM4VJRDtTUY4r/u5Eqxs4XcGPHaoPkeQ==",
|
||||||
|
"requires": {
|
||||||
|
"@wry/context": "^0.5.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/code-frame": {
|
"@babel/code-frame": {
|
||||||
"version": "7.10.1",
|
"version": "7.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
|
||||||
|
@ -1230,7 +1267,8 @@
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "13.13.12",
|
"version": "13.13.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.12.tgz",
|
||||||
"integrity": "sha512-zWz/8NEPxoXNT9YyF2osqyA9WjssZukYpgI4UYZpOjcyqwIUqWGkcCionaEb9Ki+FULyPyvNFpg/329Kd2/pbw=="
|
"integrity": "sha512-zWz/8NEPxoXNT9YyF2osqyA9WjssZukYpgI4UYZpOjcyqwIUqWGkcCionaEb9Ki+FULyPyvNFpg/329Kd2/pbw==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/prop-types": {
|
"@types/prop-types": {
|
||||||
"version": "15.7.3",
|
"version": "15.7.3",
|
||||||
|
@ -1551,15 +1589,6 @@
|
||||||
"@xtuc/long": "4.2.2"
|
"@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": {
|
"@wry/equality": {
|
||||||
"version": "0.1.11",
|
"version": "0.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.1.11.tgz",
|
||||||
|
@ -1744,93 +1773,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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": {
|
"aproba": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
|
||||||
|
@ -3239,11 +3181,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.9.0.tgz",
|
||||||
"integrity": "sha512-rV75CJkubNUroAt0qCRkjznZLoaXq/ctfMXsMvKSL84UetbSyx5REl96e8GoQ04G4Tkw0XF3STECffTOQrbzOQ=="
|
"integrity": "sha512-rV75CJkubNUroAt0qCRkjznZLoaXq/ctfMXsMvKSL84UetbSyx5REl96e8GoQ04G4Tkw0XF3STECffTOQrbzOQ=="
|
||||||
},
|
},
|
||||||
"bulma-switch": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/bulma-switch/-/bulma-switch-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-myD38zeUfjmdduq+pXabhJEe3x2hQP48l/OI+Y0fO3HdDynZUY/VJygucvEAJKRjr4HxD5DnEm4yx+oDOBXpAA=="
|
|
||||||
},
|
|
||||||
"bytes": {
|
"bytes": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||||
|
@ -5615,11 +5552,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-15.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/graphql/-/graphql-15.3.0.tgz",
|
||||||
"integrity": "sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w=="
|
"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": {
|
"graphql-tag": {
|
||||||
"version": "2.10.4",
|
"version": "2.10.4",
|
||||||
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.4.tgz",
|
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.4.tgz",
|
||||||
|
@ -6507,11 +6439,6 @@
|
||||||
"verror": "1.10.0"
|
"verror": "1.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jwt-decode": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz",
|
|
||||||
"integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk="
|
|
||||||
},
|
|
||||||
"killable": {
|
"killable": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
|
||||||
|
@ -7466,14 +7393,6 @@
|
||||||
"is-wsl": "^1.1.0"
|
"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": {
|
"original": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
|
||||||
|
@ -8048,11 +7967,6 @@
|
||||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
|
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"qs": {
|
|
||||||
"version": "6.9.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz",
|
|
||||||
"integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ=="
|
|
||||||
},
|
|
||||||
"querystring": {
|
"querystring": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
||||||
|
@ -11006,15 +10920,6 @@
|
||||||
"version": "0.8.15",
|
"version": "0.8.15",
|
||||||
"resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz",
|
"resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz",
|
||||||
"integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="
|
"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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,20 +51,10 @@
|
||||||
"webpack-dev-server": "^3.11.0"
|
"webpack-dev-server": "^3.11.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@apollo/client": "^3.0.2",
|
||||||
"@types/qs": "^6.9.3",
|
"@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.9.0",
|
"bulma": "^0.9.0",
|
||||||
"bulma-switch": "^2.0.0",
|
|
||||||
"graphql": "^15.3.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",
|
"react": "^16.12.0",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"react-redux": "^7.1.3",
|
"react-redux": "^7.1.3",
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BrowserRouter, Route, Redirect, Switch } from "react-router-dom";
|
import { BrowserRouter, Route, Redirect, Switch } from "react-router-dom";
|
||||||
import { HomePage } from './HomePage/HomePage';
|
import { HomePage } from './HomePage/HomePage';
|
||||||
import { store } from '../store/store';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { ProfilePage } from './ProfilePage/ProfilePage';
|
import { ProfilePage } from './ProfilePage/ProfilePage';
|
||||||
|
|
||||||
export class App extends React.Component {
|
export class App extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<BrowserRouter>
|
||||||
<BrowserRouter>
|
<Switch>
|
||||||
<Switch>
|
<Route path="/" exact component={HomePage} />
|
||||||
<Route path="/" exact component={HomePage} />
|
<Route path="/profile" exact component={ProfilePage} />
|
||||||
<Route path="/profile" exact component={ProfilePage} />
|
<Route component={() => <Redirect to="/" />} />
|
||||||
<Route component={() => <Redirect to="/" />} />
|
</Switch>
|
||||||
</Switch>
|
</BrowserRouter>
|
||||||
</BrowserRouter>
|
|
||||||
</Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,4 @@
|
||||||
import React, { useEffect } from 'react';
|
import React from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
|
||||||
import { fetchWorkgroups } from '../../store/actions/workgroups';
|
|
||||||
import { RootState } from '../../store/reducers/root';
|
|
||||||
import { WorkgroupsPanel } from './WorkgroupsPanel';
|
import { WorkgroupsPanel } from './WorkgroupsPanel';
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
|
|
|
@ -1,18 +1,26 @@
|
||||||
import React, { useEffect } from 'react';
|
import React from 'react';
|
||||||
import { Page } from '../Page';
|
import { Page } from '../Page';
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { RootState } from '../../store/reducers/root';
|
|
||||||
import { Dashboard } from './Dashboard';
|
import { Dashboard } from './Dashboard';
|
||||||
|
import { useUserProfileQuery } from '../../gql/queries/profile';
|
||||||
|
import { Loader } from '../Loader';
|
||||||
|
|
||||||
export function HomePage() {
|
export function HomePage() {
|
||||||
const currentUser = useSelector((state: RootState) => state.auth.currentUser);
|
const { data, loading } = useUserProfileQuery();
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Loader />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { userProfile } = (data || {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page title={currentUser ? 'Tableau de bord' : 'Accueil'}>
|
<Page title={userProfile ? 'Tableau de bord' : 'Accueil'}>
|
||||||
<div className="container is-fluid">
|
<div className="container is-fluid">
|
||||||
<section className="section">
|
<section className="section">
|
||||||
{
|
{
|
||||||
currentUser ?
|
userProfile ?
|
||||||
<Dashboard /> :
|
<Dashboard /> :
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
<div className="column is-4 is-offset-4">
|
<div className="column is-4 is-offset-4">
|
||||||
|
|
|
@ -1,44 +1,49 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
|
||||||
import { RootState } from '../../store/reducers/root';
|
|
||||||
import { fetchWorkgroups } from '../../store/actions/workgroups';
|
|
||||||
import { Workgroup } from '../../types/workgroup';
|
import { Workgroup } from '../../types/workgroup';
|
||||||
import { User } from '../../types/user';
|
import { User } from '../../types/user';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useWorkgroupsQuery } from '../../gql/queries/workgroups';
|
||||||
|
import { useUserProfileQuery } from '../../gql/queries/profile';
|
||||||
|
import { Loader } from '../Loader';
|
||||||
|
|
||||||
export function WorkgroupsPanel() {
|
export function WorkgroupsPanel() {
|
||||||
const dispatch = useDispatch();
|
const workgroupsQuery = useWorkgroupsQuery();
|
||||||
const workgroups = useSelector<RootState>(state => state.workgroups.workgroupsById);
|
const userProfileQuery = useUserProfileQuery();
|
||||||
const currentUserId = useSelector<RootState>(state => state.auth.currentUser.id);
|
const [ state, setState ] = useState({ selectedTab: 0 });
|
||||||
|
|
||||||
|
const isLoading = userProfileQuery.loading || workgroupsQuery.loading;
|
||||||
|
if (isLoading) {
|
||||||
|
return <Loader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { data: { userProfile }} = userProfileQuery;
|
||||||
|
let { data: { workgroups }} = workgroupsQuery;
|
||||||
|
|
||||||
const filterTabs = [
|
const filterTabs = [
|
||||||
{
|
{
|
||||||
label: "Mes groupes",
|
label: "Mes groupes",
|
||||||
filter: workgroups => Object.values(workgroups).filter((wg: Workgroup) => {
|
filter: workgroups => workgroups.filter((wg: Workgroup) => {
|
||||||
return wg.members.some((u: User) => u.id === currentUserId);
|
return wg.members.some((u: User) => u.id === userProfile.id);
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Ouverts",
|
label: "Ouverts",
|
||||||
filter: workgroups => Object.values(workgroups).filter((wg: Workgroup) => !wg.closedAt)
|
filter: workgroups => workgroups.filter((wg: Workgroup) => !wg.closedAt)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Clôs",
|
label: "Clôs",
|
||||||
filter: workgroups => Object.values(workgroups).filter((wg: Workgroup) => !!wg.closedAt)
|
filter: workgroups => workgroups.filter((wg: Workgroup) => !!wg.closedAt)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const [ state, setState ] = useState({ selectedTab: 0 });
|
|
||||||
|
|
||||||
const selectTab = (tabIndex: number) => {
|
const selectTab = (tabIndex: number) => {
|
||||||
setState(state => ({ ...state, selectedTab: tabIndex }));
|
setState(state => ({ ...state, selectedTab: tabIndex }));
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(fetchWorkgroups());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const workgroupsItems = filterTabs[state.selectedTab].filter(workgroups).map((wg: Workgroup) => {
|
let workgroupsItems = [];
|
||||||
|
|
||||||
|
workgroupsItems = filterTabs[state.selectedTab].filter(workgroups).map((wg: Workgroup) => {
|
||||||
return (
|
return (
|
||||||
<Link to={`/workgroups/${wg.id}`} key={`wg-${wg.id}`} className="panel-block">
|
<Link to={`/workgroups/${wg.id}`} key={`wg-${wg.id}`} className="panel-block">
|
||||||
<span className="panel-icon">
|
<span className="panel-icon">
|
||||||
|
@ -47,7 +52,7 @@ export function WorkgroupsPanel() {
|
||||||
{wg.name}
|
{wg.name}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="panel is-info">
|
<nav className="panel is-info">
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import React, { Fragment, useState } from 'react';
|
import React, { Fragment, useState } from 'react';
|
||||||
import logo from '../resources/logo.svg';
|
import logo from '../resources/logo.svg';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { RootState } from '../store/reducers/root';
|
|
||||||
import { Config } from '../config';
|
import { Config } from '../config';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useUserProfileQuery } from '../gql/queries/profile';
|
||||||
|
import { WithLoader } from './WithLoader';
|
||||||
|
|
||||||
export function Navbar() {
|
export function Navbar() {
|
||||||
const isAuthenticated = useSelector<RootState>(state => state.auth.isAuthenticated);
|
const userProfileQuery = useUserProfileQuery();
|
||||||
|
|
||||||
const [ isActive, setActive ] = useState(false);
|
const [ isActive, setActive ] = useState(false);
|
||||||
|
|
||||||
const toggleMenu = () => {
|
const toggleMenu = () => {
|
||||||
|
@ -35,28 +35,30 @@ export function Navbar() {
|
||||||
<div className={`navbar-menu ${isActive ? 'is-active' : ''}`}>
|
<div className={`navbar-menu ${isActive ? 'is-active' : ''}`}>
|
||||||
<div className="navbar-end">
|
<div className="navbar-end">
|
||||||
<div className="navbar-item">
|
<div className="navbar-item">
|
||||||
<div className="buttons">
|
<WithLoader loading={userProfileQuery.loading}>
|
||||||
{
|
<div className="buttons">
|
||||||
isAuthenticated ?
|
{
|
||||||
<Fragment>
|
userProfileQuery.data && userProfileQuery.data.userProfile ?
|
||||||
<Link to="/profile" className="button">
|
<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">
|
<span className="icon">
|
||||||
<i className="fas fa-user"></i>
|
<i className="fas fa-sign-in-alt"></i>
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
<a className="button" href={Config.logoutURL}>
|
|
||||||
<span className="icon">
|
|
||||||
<i className="fas fa-sign-out-alt"></i>
|
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</Fragment> :
|
}
|
||||||
<a className="button" href={Config.loginURL}>
|
</div>
|
||||||
<span className="icon">
|
</WithLoader>
|
||||||
<i className="fas fa-sign-in-alt"></i>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Page } from '../Page';
|
import { Page } from '../Page';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
|
||||||
import { RootState } from '../../store/reducers/root';
|
|
||||||
import { UserForm } from '../UserForm';
|
import { UserForm } from '../UserForm';
|
||||||
import { Loader } from '../Loader';
|
import { Loader } from '../Loader';
|
||||||
import { User } from '../../types/user';
|
import { User } from '../../types/user';
|
||||||
import { updateProfile } from '../../store/actions/profile';
|
import { useUserProfileQuery } from '../../gql/queries/profile';
|
||||||
|
import { useUpdateUserProfileMutation } from '../../gql/mutations/profile';
|
||||||
|
import { WithLoader } from '../WithLoader';
|
||||||
|
|
||||||
export function ProfilePage() {
|
export function ProfilePage() {
|
||||||
const currentUser = useSelector((state: RootState) => state.auth.currentUser);
|
const userProfileQuery = useUserProfileQuery();
|
||||||
const dispatch = useDispatch();
|
const [ updatProfile, updateUserProfileMutation ] = useUpdateUserProfileMutation();
|
||||||
|
const isLoading = updateUserProfileMutation.loading || userProfileQuery.loading;
|
||||||
|
|
||||||
|
const { userProfile } = (userProfileQuery.data || {});
|
||||||
|
|
||||||
const onUserChange = (user: User) => {
|
const onUserChange = (user: User) => {
|
||||||
if (currentUser.name !== user.name) {
|
if (userProfile.name !== user.name) {
|
||||||
dispatch(updateProfile({ name: user.name }))
|
updatProfile({ variables: {changes: { name: user.name }}});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,11 +27,13 @@ export function ProfilePage() {
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
<div className="column is-6 is-offset-3">
|
<div className="column is-6 is-offset-3">
|
||||||
<h2 className="is-size-2 subtitle">Mon profil</h2>
|
<h2 className="is-size-2 subtitle">Mon profil</h2>
|
||||||
|
<WithLoader loading={isLoading}>
|
||||||
{
|
{
|
||||||
currentUser ?
|
userProfile ?
|
||||||
<UserForm onChange={onUserChange} user={currentUser} /> :
|
<UserForm onChange={onUserChange} user={userProfile} /> :
|
||||||
<Loader />
|
<Loader />
|
||||||
}
|
}
|
||||||
|
</WithLoader>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -10,11 +10,11 @@ export function UserForm({ user, onChange }: UserFormProps) {
|
||||||
const [ state, setState ] = useState({
|
const [ state, setState ] = useState({
|
||||||
changed: false,
|
changed: false,
|
||||||
user: {
|
user: {
|
||||||
name: '',
|
id: user && user.id ? user.id : '',
|
||||||
email: '',
|
name: user && user.name ? user.name : '',
|
||||||
createdAt: null,
|
email: user && user.email ? user.email : '',
|
||||||
connectedAt: null,
|
createdAt: user && user.createdAt ? user.createdAt : null,
|
||||||
...user,
|
connectedAt: user && user.connectedAt ? user.connectedAt : null,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import React, { Fragment, PropsWithChildren, FunctionComponent } from 'react';
|
||||||
|
|
||||||
|
export interface WithLoaderProps {
|
||||||
|
loading?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithLoader: FunctionComponent<WithLoaderProps> = ({ loading, children }) => {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{
|
||||||
|
loading ?
|
||||||
|
<div className="loader-container">
|
||||||
|
<div className="lds-ripple">
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
</div> :
|
||||||
|
children
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
|
||||||
|
import { Config } from '../config';
|
||||||
|
import { WebSocketLink } from "@apollo/client/link/ws";
|
||||||
|
import { RetryLink } from "@apollo/client/link/retry";
|
||||||
|
import { SubscriptionClient } from "subscriptions-transport-ws";
|
||||||
|
|
||||||
|
|
||||||
|
const subscriptionClient = new SubscriptionClient(Config.subscriptionEndpoint, {
|
||||||
|
reconnect: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const link = new RetryLink().split(
|
||||||
|
(operation) => operation.operationName === 'subscription',
|
||||||
|
new WebSocketLink(subscriptionClient),
|
||||||
|
new HttpLink({ uri: Config.graphQLEndpoint, credentials: 'include' })
|
||||||
|
);
|
||||||
|
|
||||||
|
export const client = new ApolloClient<any>({
|
||||||
|
cache: new InMemoryCache(),
|
||||||
|
link: link,
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { gql, useQuery, useMutation } from '@apollo/client';
|
||||||
|
|
||||||
|
const MUTATION_UPDATE_USER_PROFILE = gql`
|
||||||
|
mutation updateUserProfile($changes: ProfileChanges!) {
|
||||||
|
updateProfile(changes: $changes) {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
createdAt,
|
||||||
|
connectedAt,
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
export function useUpdateUserProfileMutation() {
|
||||||
|
return useMutation(MUTATION_UPDATE_USER_PROFILE);
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { gql, useQuery } from '@apollo/client';
|
||||||
|
|
||||||
|
const QUERY_USER_PROFILE = gql`
|
||||||
|
query userProfile {
|
||||||
|
userProfile {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
createdAt,
|
||||||
|
connectedAt
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
export function useUserProfileQuery() {
|
||||||
|
return useQuery(QUERY_USER_PROFILE, { fetchPolicy: "network-only" });
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { gql, useQuery } from '@apollo/client';
|
||||||
|
|
||||||
|
const QUERY_WORKGROUP = gql`
|
||||||
|
query workgroups {
|
||||||
|
workgroups {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
createdAt,
|
||||||
|
closedAt,
|
||||||
|
members {
|
||||||
|
id,
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function useWorkgroupsQuery(options = {}) {
|
||||||
|
return useQuery(QUERY_WORKGROUP, options);
|
||||||
|
}
|
|
@ -2,15 +2,18 @@ import './sass/_all.scss';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { App } from './components/App';
|
import { App } from './components/App';
|
||||||
import { Config } from './config';
|
import { client } from './gql/client';
|
||||||
|
|
||||||
import '@fortawesome/fontawesome-free/js/fontawesome'
|
import '@fortawesome/fontawesome-free/js/fontawesome'
|
||||||
import '@fortawesome/fontawesome-free/js/solid'
|
import '@fortawesome/fontawesome-free/js/solid'
|
||||||
import '@fortawesome/fontawesome-free/js/regular'
|
import '@fortawesome/fontawesome-free/js/regular'
|
||||||
import '@fortawesome/fontawesome-free/js/brands'
|
import '@fortawesome/fontawesome-free/js/brands'
|
||||||
import './resources/favicon.png';
|
import './resources/favicon.png';
|
||||||
|
import { ApolloProvider } from '@apollo/client';
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<App />,
|
<ApolloProvider client={client}>
|
||||||
|
<App />
|
||||||
|
</ApolloProvider>,
|
||||||
document.getElementById('app')
|
document.getElementById('app')
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
@import 'bulma/bulma.sass';
|
@import 'bulma/bulma.sass';
|
||||||
@import 'bulma-switch/dist/css/bulma-switch.sass';
|
|
||||||
@import '_base.scss';
|
@import '_base.scss';
|
||||||
@import '_loader.scss';
|
@import '_loader.scss';
|
|
@ -1,11 +0,0 @@
|
||||||
import { Action } from "redux";
|
|
||||||
|
|
||||||
export const SET_CURRENT_USER = 'SET_CURRENT_USER';
|
|
||||||
|
|
||||||
export interface setCurrentUserAction extends Action {
|
|
||||||
email: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setCurrentUser(email: string): setCurrentUserAction {
|
|
||||||
return { type: SET_CURRENT_USER, email };
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { Action } from "redux";
|
|
||||||
import { User } from "../../types/user";
|
|
||||||
|
|
||||||
export const FETCH_PROFILE_REQUEST = 'FETCH_PROFILE_REQUEST';
|
|
||||||
export const FETCH_PROFILE_SUCCESS = 'FETCH_PROFILE_SUCCESS';
|
|
||||||
export const FETCH_PROFILE_FAILURE = 'FETCH_PROFILE_FAILURE';
|
|
||||||
|
|
||||||
export interface fetchProfileRequestAction extends Action {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface fetchProfileSuccessAction extends Action {
|
|
||||||
profile: User
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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,19 +0,0 @@
|
||||||
import { Action } from "redux";
|
|
||||||
import { Workgroup } from "../../types/workgroup";
|
|
||||||
|
|
||||||
export const FETCH_WORKGROUPS_REQUEST = 'FETCH_WORKGROUPS_REQUEST';
|
|
||||||
export const FETCH_WORKGROUPS_SUCCESS = 'FETCH_WORKGROUPS_SUCCESS';
|
|
||||||
export const FETCH_WORKGROUPS_FAILURE = 'FETCH_WORKGROUPS_FAILURE';
|
|
||||||
|
|
||||||
export interface fetchWorkgroupsRequestAction extends Action {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface fetchWorkgroupsSuccessAction extends Action {
|
|
||||||
workgroups: [Workgroup]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function fetchWorkgroups(): fetchWorkgroupsRequestAction {
|
|
||||||
return { type: FETCH_WORKGROUPS_REQUEST }
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
import { Action } from "redux";
|
|
||||||
import { User } from "../../types/user";
|
|
||||||
import { SET_CURRENT_USER, setCurrentUserAction } from "../actions/auth";
|
|
||||||
import { FETCH_PROFILE_SUCCESS, fetchProfileSuccessAction, updateProfileSuccessAction, UPDATE_PROFILE_SUCCESS, updateProfileRequestAction } from "../actions/profile";
|
|
||||||
|
|
||||||
export interface AuthState {
|
|
||||||
isAuthenticated: boolean
|
|
||||||
currentUser: User
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultState = {
|
|
||||||
isAuthenticated: false,
|
|
||||||
currentUser: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function authReducer(state = defaultState, action: Action): AuthState {
|
|
||||||
switch (action.type) {
|
|
||||||
case SET_CURRENT_USER:
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSetCurrentUser(state: AuthState, { email }: setCurrentUserAction): AuthState {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isAuthenticated: true,
|
|
||||||
currentUser: {
|
|
||||||
id: '',
|
|
||||||
email
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleFetchProfileSuccess(state: AuthState, { profile }: fetchProfileSuccessAction): AuthState {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isAuthenticated: true,
|
|
||||||
currentUser: {
|
|
||||||
...profile,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleUpdateProfileSuccess(state: AuthState, { profile }: updateProfileSuccessAction): AuthState {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isAuthenticated: true,
|
|
||||||
currentUser: {
|
|
||||||
...profile,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,32 +0,0 @@
|
||||||
import { Action } from "redux";
|
|
||||||
|
|
||||||
export interface FlagsState {
|
|
||||||
actions: { [actionName: string]: ActionState }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ActionState {
|
|
||||||
isLoading: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultState = {
|
|
||||||
actions: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
export function flagsReducer(state = defaultState, action: Action): FlagsState {
|
|
||||||
const matches = (/^(.*)_((SUCCESS)|(FAILURE)|(REQUEST))$/).exec(action.type);
|
|
||||||
|
|
||||||
if(!matches) return state;
|
|
||||||
|
|
||||||
const actionPrefix = matches[1];
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
actions: {
|
|
||||||
...state.actions,
|
|
||||||
[actionPrefix]: {
|
|
||||||
isLoading: matches[2] === 'REQUEST'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { combineReducers } from 'redux';
|
|
||||||
import { flagsReducer, FlagsState } from './flags';
|
|
||||||
import { authReducer, AuthState } from './auth';
|
|
||||||
import { workgroupsReducer, WorkgroupsState } from './workgroups';
|
|
||||||
|
|
||||||
export interface RootState {
|
|
||||||
auth: AuthState,
|
|
||||||
flags: FlagsState,
|
|
||||||
workgroups: WorkgroupsState,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const rootReducer = combineReducers({
|
|
||||||
flags: flagsReducer,
|
|
||||||
auth: authReducer,
|
|
||||||
workgroups: workgroupsReducer,
|
|
||||||
});
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { Action } from "redux";
|
|
||||||
import { User } from "../../types/user";
|
|
||||||
import { SET_CURRENT_USER, setCurrentUserAction } from "../actions/auth";
|
|
||||||
import { FETCH_PROFILE_SUCCESS, fetchProfileSuccessAction, updateProfileSuccessAction, UPDATE_PROFILE_SUCCESS, updateProfileRequestAction } from "../actions/profile";
|
|
||||||
import { Workgroup } from "../../types/workgroup";
|
|
||||||
import { FETCH_WORKGROUPS_SUCCESS, fetchWorkgroupsSuccessAction } from "../actions/workgroups";
|
|
||||||
|
|
||||||
export interface WorkgroupsState {
|
|
||||||
workgroupsById: { [id in string]: Workgroup }
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultState = {
|
|
||||||
workgroupsById: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
export function workgroupsReducer(state = defaultState, action: Action): WorkgroupsState {
|
|
||||||
switch (action.type) {
|
|
||||||
case FETCH_WORKGROUPS_SUCCESS:
|
|
||||||
return handleFetchWorkgroups(state, action as fetchWorkgroupsSuccessAction);
|
|
||||||
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleFetchWorkgroups(state: WorkgroupsState, { workgroups }: fetchWorkgroupsSuccessAction): WorkgroupsState {
|
|
||||||
const workgroupsById = {
|
|
||||||
...state.workgroupsById,
|
|
||||||
};
|
|
||||||
|
|
||||||
workgroups.forEach(wg => {
|
|
||||||
workgroupsById[wg.id] = wg;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
workgroupsById,
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,21 +0,0 @@
|
||||||
import { UnauthorizedError } from "../../util/daddy";
|
|
||||||
import { all, takeEvery } from 'redux-saga/effects';
|
|
||||||
|
|
||||||
export function* failureRootSaga() {
|
|
||||||
yield all([
|
|
||||||
takeEvery(patternFromRegExp(/^.*_FAILURE/), failuresSaga),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* failuresSaga(action) {
|
|
||||||
if (action.error instanceof UnauthorizedError) {
|
|
||||||
// TODO Implements better authorization error handling
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patternFromRegExp(re: any) {
|
|
||||||
return (action: any) => {
|
|
||||||
return re.test(action.type);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { all, put } from "redux-saga/effects";
|
|
||||||
import { fetchProfile } from "../actions/profile";
|
|
||||||
|
|
||||||
export function* initRootSaga() {
|
|
||||||
yield all([
|
|
||||||
fetchUserProfileSaga(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* fetchUserProfileSaga() {
|
|
||||||
yield put(fetchProfile());
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
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,14 +0,0 @@
|
||||||
import { all } from 'redux-saga/effects';
|
|
||||||
import { failureRootSaga } from './failure';
|
|
||||||
import { initRootSaga } from './init';
|
|
||||||
import { profileRootSaga } from './profile';
|
|
||||||
import { workgroupsRootSaga } from './workgroups';
|
|
||||||
|
|
||||||
export function* rootSaga() {
|
|
||||||
yield all([
|
|
||||||
initRootSaga(),
|
|
||||||
failureRootSaga(),
|
|
||||||
profileRootSaga(),
|
|
||||||
workgroupsRootSaga(),
|
|
||||||
]);
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
import { getClient } from "../../util/daddy";
|
|
||||||
import { Config } from "../../config";
|
|
||||||
import { all, takeLatest, put } from "redux-saga/effects";
|
|
||||||
import { FETCH_WORKGROUPS_SUCCESS, FETCH_WORKGROUPS_FAILURE, FETCH_WORKGROUPS_REQUEST } from "../actions/workgroups";
|
|
||||||
import { Workgroup } from "../../types/workgroup";
|
|
||||||
|
|
||||||
export function* workgroupsRootSaga() {
|
|
||||||
yield all([
|
|
||||||
takeLatest(FETCH_WORKGROUPS_REQUEST, fetchWorkgroupsSaga),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* fetchWorkgroupsSaga() {
|
|
||||||
const client = getClient(Config.graphQLEndpoint, Config.subscriptionEndpoint);
|
|
||||||
|
|
||||||
let workgroups: [Workgroup];
|
|
||||||
try {
|
|
||||||
workgroups = yield client.fetchWorkgroups().then(result => result.workgroups);
|
|
||||||
} catch(err) {
|
|
||||||
yield put({ type: FETCH_WORKGROUPS_FAILURE, err });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield put({type: FETCH_WORKGROUPS_SUCCESS, workgroups });
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
export function selectFlagsIsLoading(state: any, ...actionPrefixes: any[]) {
|
|
||||||
const { actions } = state.flags;
|
|
||||||
return actionPrefixes.reduce((isLoading, prefix) => {
|
|
||||||
if (!(prefix in actions)) return isLoading;
|
|
||||||
return isLoading || actions[prefix].isLoading;
|
|
||||||
}, false);
|
|
||||||
};
|
|
|
@ -1,30 +0,0 @@
|
||||||
import { createStore, applyMiddleware, compose } from 'redux'
|
|
||||||
import createSagaMiddleware from 'redux-saga'
|
|
||||||
import { rootReducer } from './reducers/root'
|
|
||||||
import { rootSaga } from './sagas/root'
|
|
||||||
|
|
||||||
let reduxMiddlewares = [];
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const createLogger = require('redux-logger').createLogger;
|
|
||||||
const loggerMiddleware = createLogger({
|
|
||||||
collapsed: true,
|
|
||||||
diff: true
|
|
||||||
});
|
|
||||||
reduxMiddlewares.push(loggerMiddleware);
|
|
||||||
}
|
|
||||||
|
|
||||||
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
|
||||||
|
|
||||||
// create the saga middleware
|
|
||||||
const sagaMiddleware = createSagaMiddleware()
|
|
||||||
reduxMiddlewares.push(sagaMiddleware);
|
|
||||||
|
|
||||||
// mount it on the Store
|
|
||||||
export const store = createStore(
|
|
||||||
rootReducer,
|
|
||||||
composeEnhancers(applyMiddleware(...reduxMiddlewares)),
|
|
||||||
)
|
|
||||||
|
|
||||||
// then run the saga
|
|
||||||
sagaMiddleware.run(rootSaga);
|
|
|
@ -1,134 +0,0 @@
|
||||||
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[]) {
|
|
||||||
super(...args)
|
|
||||||
Object.setPrototypeOf(this, UnauthorizedError.prototype);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let client: DaddyClient
|
|
||||||
|
|
||||||
export function getClient(graphQLEndpoint: string, subscriptionEndpoint: string): DaddyClient {
|
|
||||||
if (!client) {
|
|
||||||
client = new DaddyClient(graphQLEndpoint, subscriptionEndpoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DaddyClient {
|
|
||||||
|
|
||||||
gql: ApolloClient<InMemoryCache>
|
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchProfile() {
|
|
||||||
return this.gql.query({
|
|
||||||
query: gql`
|
|
||||||
query {
|
|
||||||
userProfile {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
email,
|
|
||||||
createdAt,
|
|
||||||
connectedAt
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
})
|
|
||||||
.then(this.assertAuthorization)
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchWorkgroups() {
|
|
||||||
return this.gql.query({
|
|
||||||
query: gql`
|
|
||||||
query {
|
|
||||||
workgroups {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
createdAt,
|
|
||||||
closedAt,
|
|
||||||
members {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
})
|
|
||||||
.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)
|
|
||||||
}
|
|
||||||
|
|
||||||
assertAuthorization({ status, data }: any) {
|
|
||||||
if (status === 401) return Promise.reject(new UnauthorizedError());
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -33,7 +33,6 @@ func Mount(r *chi.Mux, config *config.Config) error {
|
||||||
AllowCredentials: config.HTTP.CORS.AllowCredentials,
|
AllowCredentials: config.HTTP.CORS.AllowCredentials,
|
||||||
Debug: config.Debug,
|
Debug: config.Debug,
|
||||||
}).Handler)
|
}).Handler)
|
||||||
r.Use(oidc.Middleware)
|
|
||||||
r.Use(session.UserEmailMiddleware)
|
r.Use(session.UserEmailMiddleware)
|
||||||
|
|
||||||
gql := handler.New(
|
gql := handler.New(
|
||||||
|
|
|
@ -21,7 +21,9 @@ func UserEmailMiddleware(next http.Handler) http.Handler {
|
||||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
userEmail, err := GetUserEmail(w, r)
|
userEmail, err := GetUserEmail(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(errors.Wrap(err, "could not find user email"))
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := WithUserEmail(r.Context(), userEmail)
|
ctx := WithUserEmail(r.Context(), userEmail)
|
||||||
|
|
Loading…
Reference in New Issue