diff --git a/.env.dev b/.env.dev deleted file mode 100644 index 03e8034..0000000 --- a/.env.dev +++ /dev/null @@ -1,6 +0,0 @@ -# Server -SERVER_HOST=http://localhost:8000 - -# OpenID Connect -OPENID_CONNECT_DISCOVERY_URL=http://localhost:8080/auth/realms/tdp_server/.well-known/openid-configuration -OPENID_CLIENT_ID=tdp_server diff --git a/config.dev.json b/config.dev.json new file mode 100644 index 0000000..de3e0da --- /dev/null +++ b/config.dev.json @@ -0,0 +1,9 @@ +{ + "apiBasePath": "http://localhost:8000", + "oidc": { + "discoveryUrl": "http://localhost:8080/auth/realms/tdp_server/.well-known/openid-configuration", + "redirectUri": "http://localhost:3000/", + "clientId": "tdp_server", + "scope": "openid tdp_server:read tdp_server:write tdp_server:execute" + } +} diff --git a/env/export/templates/default.conf.template b/env/export/templates/default.conf.template index e728a1a..821a1df 100644 --- a/env/export/templates/default.conf.template +++ b/env/export/templates/default.conf.template @@ -15,10 +15,14 @@ server { try_files $uri $uri.html $uri/ =404; } - #error_page 404 /404.html; + location /config.json { + root /usr/share/nginx/html; + index config.json; + + try_files $uri $uri/ =404; + } # redirect server error pages to the static page /50x.html - # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; diff --git a/package-lock.json b/package-lock.json index e11dd8c..8801548 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,12 +10,14 @@ "hasInstallScript": true, "dependencies": { "@heroicons/react": "^2.0.12", + "@reduxjs/toolkit": "^1.9.1", "next": "^13.0.0", "oidc-client-ts": "^2.1.0", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.2.0", "react-oidc-context": "^2.2.0", + "react-redux": "^8.0.5", "react-select": "^5.7.0", "react-toastify": "^9.1.1" }, @@ -1515,6 +1517,29 @@ "node": ">= 8" } }, + "node_modules/@reduxjs/toolkit": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.1.tgz", + "integrity": "sha512-HikrdY+IDgRfRYlCTGUQaiCxxDDgM1mQrRbZ6S1HFZX5ZYuJ4o8EstNmhTwHdPl2rTmLxzwSu0b3AyeyTlR+RA==", + "dependencies": { + "immer": "^9.0.16", + "redux": "^4.2.0", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.7" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", @@ -1616,9 +1641,9 @@ } }, "node_modules/@types/react-redux": { - "version": "7.1.24", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz", - "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==", + "version": "7.1.25", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz", + "integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==", "dependencies": { "@types/hoist-non-react-statics": "^3.3.0", "@types/react": "*", @@ -1639,6 +1664,11 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@typescript-eslint/parser": { "version": "5.38.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.38.1.tgz", @@ -3857,6 +3887,15 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "9.0.18", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.18.tgz", + "integrity": "sha512-eAPNpsj7Ax1q6Y/3lm2PmlwRcFzpON7HSNQ3ru5WQH1/PSpnyed/HpNOELl2CxLKoj4r+bAHgdyKqW5gc2Se1A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -5498,6 +5537,35 @@ "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-beautiful-dnd/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/react-beautiful-dnd/node_modules/react-redux": { + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", + "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "dependencies": { + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -5528,33 +5596,47 @@ } }, "node_modules/react-redux": { - "version": "7.2.9", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", - "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", + "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", "dependencies": { - "@babel/runtime": "^7.15.4", - "@types/react-redux": "^7.1.20", + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^17.0.2" + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" }, "peerDependencies": { - "react": "^16.8.3 || ^17 || ^18" + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4" }, "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, "react-dom": { "optional": true }, "react-native": { "optional": true + }, + "redux": { + "optional": true } } }, "node_modules/react-redux/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-select": { "version": "5.7.0", @@ -5793,6 +5875,14 @@ "@babel/runtime": "^7.9.2" } }, + "node_modules/redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "peerDependencies": { + "redux": "^4" + } + }, "node_modules/regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", @@ -5845,6 +5935,11 @@ "node": ">=0.10.0" } }, + "node_modules/reselect": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", + "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -8028,6 +8123,17 @@ "fastq": "^1.6.0" } }, + "@reduxjs/toolkit": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.1.tgz", + "integrity": "sha512-HikrdY+IDgRfRYlCTGUQaiCxxDDgM1mQrRbZ6S1HFZX5ZYuJ4o8EstNmhTwHdPl2rTmLxzwSu0b3AyeyTlR+RA==", + "requires": { + "immer": "^9.0.16", + "redux": "^4.2.0", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.7" + } + }, "@rushstack/eslint-patch": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", @@ -8129,9 +8235,9 @@ } }, "@types/react-redux": { - "version": "7.1.24", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz", - "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==", + "version": "7.1.25", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz", + "integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==", "requires": { "@types/hoist-non-react-statics": "^3.3.0", "@types/react": "*", @@ -8152,6 +8258,11 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, + "@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "@typescript-eslint/parser": { "version": "5.38.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.38.1.tgz", @@ -9752,6 +9863,11 @@ "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, + "immer": { + "version": "9.0.18", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.18.tgz", + "integrity": "sha512-eAPNpsj7Ax1q6Y/3lm2PmlwRcFzpON7HSNQ3ru5WQH1/PSpnyed/HpNOELl2CxLKoj4r+bAHgdyKqW5gc2Se1A==" + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -10875,6 +10991,26 @@ "react-redux": "^7.2.0", "redux": "^4.0.4", "use-memo-one": "^1.1.1" + }, + "dependencies": { + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "react-redux": { + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", + "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "requires": { + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + } + } } }, "react-dom": { @@ -10898,22 +11034,22 @@ "requires": {} }, "react-redux": { - "version": "7.2.9", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", - "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", + "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", "requires": { - "@babel/runtime": "^7.15.4", - "@types/react-redux": "^7.1.20", + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^17.0.2" + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" }, "dependencies": { "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" } } }, @@ -11106,6 +11242,12 @@ "@babel/runtime": "^7.9.2" } }, + "redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "requires": {} + }, "regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", @@ -11140,6 +11282,11 @@ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, + "reselect": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", + "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", diff --git a/package.json b/package.json index 9d2ac1e..0b333c8 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dev": "next dev", "build": "next build", "start": "next start", - "export": "next build && next export", + "export": "next build && next export && cp config.dev.json out/config.json", "lint": "next lint", "prepare": "husky install", "prettier:check": "npx prettier --check .", @@ -15,12 +15,14 @@ }, "dependencies": { "@heroicons/react": "^2.0.12", + "@reduxjs/toolkit": "^1.9.1", "next": "^13.0.0", "oidc-client-ts": "^2.1.0", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.2.0", "react-oidc-context": "^2.2.0", + "react-redux": "^8.0.5", "react-select": "^5.7.0", "react-toastify": "^9.1.1" }, diff --git a/src/components/Login/hooks/useServerStatus.ts b/src/components/Login/hooks/useServerStatus.ts index a76bd27..e1e0701 100644 --- a/src/components/Login/hooks/useServerStatus.ts +++ b/src/components/Login/hooks/useServerStatus.ts @@ -1,21 +1,24 @@ import { useCallback, useEffect, useState } from 'react' -import config from 'src/config' +import { useSelectConfig } from 'src/features/config/hooks' export function useServerStatus() { const [data, setData] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) + const { + value: { apiBasePath }, + } = useSelectConfig() const fetchServerStatus = useCallback(async () => { try { - const res = await fetch(config.apiBasePath) + const res = await fetch(apiBasePath) setData(!!res) } catch (e) { setError(e) } finally { setLoading(false) } - }, []) + }, [apiBasePath]) const fetchServerStatusPeriodically = useCallback(() => { const interval = setInterval(fetchServerStatus, 1000) diff --git a/src/config/index.ts b/src/config/index.ts deleted file mode 100644 index 2be4cac..0000000 --- a/src/config/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -const apiConfig = { - apiBasePath: process.env.SERVER_HOST || 'http://localhost:8000', - oidcDiscoveryUrl: - process.env.OPENID_CONNECT_DISCOVERY_URL || - 'http://localhost:8080/auth/realms/tdp_server/.well-known/openid-configuration', - oidcConfig: { - oidcClientId: process.env.OPENID_CLIENT_ID || 'tdp_server', - authority: 'http://localhost:8080/auth/realms/tdp_server', - redirectUri: 'http://localhost:3000/', - postLogoutRedirectUri: 'http://localhost:3000/', - scope: [ - 'openid', - 'tdp_server:read', - 'tdp_server:write', - 'tdp_server:execute', - ].join(' '), - }, -} - -export default apiConfig diff --git a/src/contexts/AuthContextProvider.tsx b/src/contexts/AuthContextProvider.tsx index a2bdfa8..764e5fe 100644 --- a/src/contexts/AuthContextProvider.tsx +++ b/src/contexts/AuthContextProvider.tsx @@ -1,34 +1,36 @@ -import { useState, useEffect } from 'react' -import { AuthProvider } from 'react-oidc-context' -import type { AuthProviderProps } from 'react-oidc-context' -import config from 'src/config' -import { LoginPortal } from 'src/components/Login' +import { useEffect, useState } from 'react' import { WebStorageStateStore } from 'oidc-client-ts' +import { AuthProvider, AuthProviderProps } from 'react-oidc-context' +import { LoginPortal } from 'src/components/Login' +import { useSelectConfig } from 'src/features/config/hooks' -export function AuthContextProvider({ children }): JSX.Element { +export function AuthContextProvider({ children }) { + const { value: config } = useSelectConfig() const [oidcConfig, setOidcConfig] = useState(null) useEffect(() => { - async function fetchAuthority() { - const response = await fetch(config.oidcDiscoveryUrl) - const { issuer }: { issuer: string } = await response.json() + async function createOidcConfig() { + const response = await fetch(config.oidc.discoveryUrl) + const { issuer } = await response.json() setOidcConfig({ authority: issuer, - client_id: config.oidcConfig.oidcClientId, - redirect_uri: config.oidcConfig.redirectUri, - scope: config.oidcConfig.scope, - post_logout_redirect_uri: config.oidcConfig.postLogoutRedirectUri, - userStore: new WebStorageStateStore({ store: localStorage }), + client_id: config.oidc.clientId, + redirect_uri: config.oidc.redirectUri, + scope: config.oidc.scope, + post_logout_redirect_uri: config.oidc.redirectUri, + userStore: + typeof window !== 'undefined' && + new WebStorageStateStore({ store: localStorage }), }) } - fetchAuthority() - }, []) + createOidcConfig() + }, [config]) + + if (!oidcConfig) return null - if (oidcConfig) - return ( - - {children} - - ) - return

Loading oidc provider...

+ return ( + + {children} + + ) } diff --git a/src/contexts/TdpClientContext.tsx b/src/contexts/TdpClientContext.tsx index 28e2977..eb40548 100644 --- a/src/contexts/TdpClientContext.tsx +++ b/src/contexts/TdpClientContext.tsx @@ -1,18 +1,22 @@ import { createContext, useContext, useMemo } from 'react' +import { useAuth } from 'react-oidc-context' import { Configuration, createTdpClientInstance, TdpClient } from 'src/clients' -import config from 'src/config' +import { useSelectConfig } from 'src/features/config' import { authenticationMiddleware, toastErrorMiddleware } from 'src/middlewares' const TdpClientContext = createContext(null) export const TdpClientContextProvider = ({ children }) => { + const { value: config } = useSelectConfig() + const { user } = useAuth() + const tdpClient = useMemo(() => { const configuration = new Configuration({ basePath: config.apiBasePath, - middleware: [authenticationMiddleware, toastErrorMiddleware], + middleware: [authenticationMiddleware(user), toastErrorMiddleware], }) return createTdpClientInstance(configuration) - }, []) + }, [config.apiBasePath, user]) return ( diff --git a/src/features/config/LoadingConfig.tsx b/src/features/config/LoadingConfig.tsx new file mode 100644 index 0000000..761ec07 --- /dev/null +++ b/src/features/config/LoadingConfig.tsx @@ -0,0 +1,29 @@ +import { useEffect } from 'react' +import { useAppDispatch } from 'src/store' +import { fetchConfig } from './configSlice' +import { useSelectConfig } from './hooks' + +export function LoadingConfig({ children }) { + const dispatch = useAppDispatch() + const { status, error } = useSelectConfig() + + useEffect(() => { + dispatch(fetchConfig()) + }, [dispatch]) + + if (status === 'succeeded') return children + + if (status === 'loading') + return ( +
+
Loading...
+
+ ) + + if (status === 'failed') + return ( +
+
Error: {error}
+
+ ) +} diff --git a/src/features/config/configSlice.ts b/src/features/config/configSlice.ts new file mode 100644 index 0000000..aebfca9 --- /dev/null +++ b/src/features/config/configSlice.ts @@ -0,0 +1,56 @@ +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit' +import configJson from '../../../config.dev.json' + +type Config = { + apiBasePath: string + oidc: { + discoveryUrl: string + redirectUri: string + clientId: string + scope: string + } +} + +const initialState = { + value: {}, + status: 'loading', + error: null, +} as { + value: Config + status: 'loading' | 'succeeded' | 'failed' + error: string | null +} + +export const fetchConfig = createAsyncThunk('config/fetch', async () => { + if (process.env.NODE_ENV === 'development') { + return configJson + } else { + const response = await fetch('/config.json') + return await response.json() + } +}) + +const configSlice = createSlice({ + name: 'config', + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchConfig.pending, (state) => { + state.status = 'loading' + }) + .addCase( + fetchConfig.fulfilled, + (state, action: PayloadAction) => { + state.value = action.payload + state.status = 'succeeded' + } + ) + .addCase(fetchConfig.rejected, (state, action) => { + state.error = action.error.message + state.status = 'failed' + }) + }, +}) + +export default configSlice.reducer diff --git a/src/features/config/hooks.ts b/src/features/config/hooks.ts new file mode 100644 index 0000000..6356ff8 --- /dev/null +++ b/src/features/config/hooks.ts @@ -0,0 +1,3 @@ +import { useAppSelector } from 'src/store' + +export const useSelectConfig = () => useAppSelector((state) => state.config) diff --git a/src/features/config/index.ts b/src/features/config/index.ts new file mode 100644 index 0000000..6b54304 --- /dev/null +++ b/src/features/config/index.ts @@ -0,0 +1,3 @@ +export * from './configSlice' +export * from './hooks' +export * from './LoadingConfig' diff --git a/src/middlewares/authentification.ts b/src/middlewares/authentification.ts index 3272536..818dbd1 100644 --- a/src/middlewares/authentification.ts +++ b/src/middlewares/authentification.ts @@ -1,18 +1,19 @@ +import { User } from 'oidc-client-ts' import type { FetchParams, Middleware } from 'src/clients' -import { getUser } from 'src/utils' -export const authenticationMiddleware: Middleware = { - pre: async (ctx): Promise => { - const user = getUser() - return { - ...ctx, - init: { - ...ctx.init, - headers: new Headers({ - ...ctx.init.headers, - Authorization: `Bearer ${user.access_token}`, - }), - }, - } - }, +export function authenticationMiddleware(user: User): Middleware { + return { + pre: async (ctx): Promise => { + return { + ...ctx, + init: { + ...ctx.init, + headers: new Headers({ + ...ctx.init.headers, + Authorization: `Bearer ${user.access_token}`, + }), + }, + } + }, + } } diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index dea9526..b86156a 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,7 +1,11 @@ import type { ReactElement, ReactNode } from 'react' import type { NextPage } from 'next' import type { AppProps } from 'next/app' +import { Provider } from 'react-redux' import { AuthContextProvider, TdpClientContextProvider } from 'src/contexts' +import store from 'src/store' +import { LoadingConfig } from 'src/features/config' + import '../styles/globals.css' export type NextPageWithLayout

= NextPage & { @@ -16,10 +20,14 @@ export default function MyApp({ Component, pageProps }: AppPropsWithLayout) { // Use the layout defined at the page level, if available const getLayout = Component.getLayout ?? ((page) => page) return ( - - - {getLayout()} - - + + + + + {getLayout()} + + + + ) } diff --git a/src/store.ts b/src/store.ts new file mode 100644 index 0000000..5f355ca --- /dev/null +++ b/src/store.ts @@ -0,0 +1,17 @@ +import { configureStore } from '@reduxjs/toolkit' +import { useSelector, TypedUseSelectorHook, useDispatch } from 'react-redux' +import configSlice from './features/config/configSlice' + +const store = configureStore({ + reducer: { + config: configSlice, + }, +}) + +export type RootState = ReturnType +export const useAppSelector: TypedUseSelectorHook = useSelector + +export type AppDispatch = typeof store.dispatch +export const useAppDispatch: () => AppDispatch = useDispatch + +export default store diff --git a/src/utils/getUser.ts b/src/utils/getUser.ts deleted file mode 100644 index 87a8997..0000000 --- a/src/utils/getUser.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { User } from 'oidc-client-ts' -import config from 'src/config' - -/** - * Get user infos stored by react-oidc-context in local storage - * outside of the `AuthProvider` - * @returns authenticated user infos - * @see {@link https://github.com/authts/react-oidc-context} - */ -export function getUser() { - const oidcStorage = localStorage.getItem( - `oidc.user:${config.oidcConfig.authority}:${config.oidcConfig.oidcClientId}` - ) - if (!oidcStorage) { - return null - } - - return User.fromStorageString(oidcStorage) -}