Merge pull request #2 from Inforitnl/master

Sync back to private stuff
This commit is contained in:
Rick van Lieshout 2022-07-26 11:18:17 +02:00 committed by GitHub
commit e733c4a9f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 503 additions and 73 deletions

View File

@ -5,13 +5,16 @@
"deepmerge",
"flexbugs",
"Immer",
"Keycloak",
"languagedetector",
"luxon",
"oidc",
"pmmmwh",
"preinstall",
"reduxjs",
"SVGR",
"tailwindcss",
"tendersguru",
"testid",
"typeahead",
"uncompiled"

View File

@ -68,5 +68,9 @@
"",
"export default ${1:${TM_FILENAME_BASE}}Slice.reducer;"
]
},
"react-i18next useTranslate hook": {
"prefix": ["useTranslation", "translate", "i18-trans"],
"body": ["const [translate] = useTranslation();"]
}
}

View File

@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.5.0] - 2022-06-26
- Added react-oidc (use demo/demo)
- Added an example of an authentication protected page (tenders)
- Added an example with the built in proxy (to combat CORS) (tendersguru)
## [0.4.1] - 2022-06-26
- Moved examples into example directory
- Moved routes to separate file
- Used route constants
## [0.4.0] - 2022-07-25
- Added the possibility to override partial configs during deployments

View File

@ -17,6 +17,7 @@ Includes:
- [Getting started](#getting-started)
- [Project structure](#project-structure)
- ["Forking" outside of Github](#forking-outside-of-github)
- [Configuration](#configuration)
- [Using the `config.ts` file](#using-the-configts-file)
- [adding values](#adding-values)
@ -56,6 +57,17 @@ Only the important files are shown
└── tsconfig.json
```
## "Forking" outside of Github
To use this base in other git software (not Github) you will have to manually manage the upstream.
Go into your existing repo and execute the following commands:
1. `git remote add upstream <clone-url>`
2. `git pull upstream master` # or other branchname
3. `git push`
Then, when you need to sync again you can repeat step 2 and 3
## Configuration
This starter kit comes with runtime configuration out-of-the-box.

142
package-lock.json generated
View File

@ -1,15 +1,16 @@
{
"name": "react-starter-kit",
"version": "0.4.0",
"version": "0.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "react-starter-kit",
"version": "0.4.0",
"version": "0.4.1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@axa-fr/react-oidc": "^6.0.0-beta10",
"@reduxjs/toolkit": "^1.8.3",
"deepmerge": "^4.2.2",
"i18next-http-backend": "^1.4.1",
@ -60,6 +61,7 @@
"file-loader": "^6.2.0",
"fs-extra": "^10.1.0",
"html-webpack-plugin": "^5.5.0",
"http-proxy-middleware": "^2.0.6",
"husky": "^8.0.1",
"i18next": "^21.8.14",
"i18next-browser-languagedetector": "^6.1.4",
@ -112,6 +114,18 @@
"node": ">=6.0.0"
}
},
"node_modules/@axa-fr/react-oidc": {
"version": "6.0.0-beta10",
"resolved": "https://registry.npmjs.org/@axa-fr/react-oidc/-/react-oidc-6.0.0-beta10.tgz",
"integrity": "sha512-BWHuIn9b+5O35It+V3m/8MWAV6pOjoMy2SihKfeDlsQs2Bp4HMBb4x46zygZ1IGs/u43jtTsnZkrPhwJU6VdqQ==",
"dependencies": {
"@openid/appauth": "1.3.1"
},
"peerDependencies": {
"react": "x",
"react-dom": "x"
}
},
"node_modules/@babel/code-frame": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
@ -3309,6 +3323,32 @@
"node": ">= 8"
}
},
"node_modules/@openid/appauth": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@openid/appauth/-/appauth-1.3.1.tgz",
"integrity": "sha512-e54kpi219wES2ijPzeHe1kMnT8VKH8YeTd1GAn9BzVBmutz3tBgcG1y8a4pziNr4vNjFnuD4W446Ua7ELnNDiA==",
"dependencies": {
"@types/base64-js": "^1.3.0",
"@types/jquery": "^3.5.5",
"base64-js": "^1.5.1",
"follow-redirects": "^1.13.3",
"form-data": "^4.0.0",
"opener": "^1.5.2"
}
},
"node_modules/@openid/appauth/node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz",
@ -4019,6 +4059,11 @@
"@babel/types": "^7.3.0"
}
},
"node_modules/@types/base64-js": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@types/base64-js/-/base64-js-1.3.0.tgz",
"integrity": "sha512-ZmI0sZGAUNXUfMWboWwi4LcfpoVUYldyN6Oe0oJ5cCsHDU/LlRq8nQKPXhYLOx36QYSW9bNIb1vvRrD6K7Llgw=="
},
"node_modules/@types/body-parser": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
@ -4203,6 +4248,14 @@
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
"dev": true
},
"node_modules/@types/jquery": {
"version": "3.5.14",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz",
"integrity": "sha512-X1gtMRMbziVQkErhTQmSe2jFwwENA/Zr+PprCkF63vFq+Yt5PZ4AlKqgmeNlwgn7dhsXEK888eIW2520EpC+xg==",
"dependencies": {
"@types/sizzle": "*"
}
},
"node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@ -4338,8 +4391,7 @@
"node_modules/@types/sizzle": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz",
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
"dev": true
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ=="
},
"node_modules/@types/sockjs": {
"version": "0.3.33",
@ -5252,8 +5304,7 @@
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/at-least-node": {
"version": "1.0.0",
@ -5666,7 +5717,6 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true,
"funding": [
{
"type": "github",
@ -6354,7 +6404,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
@ -7540,7 +7589,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
@ -9636,7 +9684,6 @@
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
"dev": true,
"funding": [
{
"type": "individual",
@ -15653,6 +15700,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
"bin": {
"opener": "bin/opener-bin.js"
}
},
"node_modules/optionator": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@ -21337,6 +21392,14 @@
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@axa-fr/react-oidc": {
"version": "6.0.0-beta10",
"resolved": "https://registry.npmjs.org/@axa-fr/react-oidc/-/react-oidc-6.0.0-beta10.tgz",
"integrity": "sha512-BWHuIn9b+5O35It+V3m/8MWAV6pOjoMy2SihKfeDlsQs2Bp4HMBb4x46zygZ1IGs/u43jtTsnZkrPhwJU6VdqQ==",
"requires": {
"@openid/appauth": "1.3.1"
}
},
"@babel/code-frame": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
@ -23588,6 +23651,31 @@
"fastq": "^1.6.0"
}
},
"@openid/appauth": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@openid/appauth/-/appauth-1.3.1.tgz",
"integrity": "sha512-e54kpi219wES2ijPzeHe1kMnT8VKH8YeTd1GAn9BzVBmutz3tBgcG1y8a4pziNr4vNjFnuD4W446Ua7ELnNDiA==",
"requires": {
"@types/base64-js": "^1.3.0",
"@types/jquery": "^3.5.5",
"base64-js": "^1.5.1",
"follow-redirects": "^1.13.3",
"form-data": "^4.0.0",
"opener": "^1.5.2"
},
"dependencies": {
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
}
}
},
"@pmmmwh/react-refresh-webpack-plugin": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz",
@ -24067,6 +24155,11 @@
"@babel/types": "^7.3.0"
}
},
"@types/base64-js": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@types/base64-js/-/base64-js-1.3.0.tgz",
"integrity": "sha512-ZmI0sZGAUNXUfMWboWwi4LcfpoVUYldyN6Oe0oJ5cCsHDU/LlRq8nQKPXhYLOx36QYSW9bNIb1vvRrD6K7Llgw=="
},
"@types/body-parser": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
@ -24244,6 +24337,14 @@
}
}
},
"@types/jquery": {
"version": "3.5.14",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz",
"integrity": "sha512-X1gtMRMbziVQkErhTQmSe2jFwwENA/Zr+PprCkF63vFq+Yt5PZ4AlKqgmeNlwgn7dhsXEK888eIW2520EpC+xg==",
"requires": {
"@types/sizzle": "*"
}
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@ -24379,8 +24480,7 @@
"@types/sizzle": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz",
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
"dev": true
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ=="
},
"@types/sockjs": {
"version": "0.3.33",
@ -25058,8 +25158,7 @@
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"at-least-node": {
"version": "1.0.0",
@ -25369,8 +25468,7 @@
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"batch": {
"version": "0.6.1",
@ -25874,7 +25972,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"requires": {
"delayed-stream": "~1.0.0"
}
@ -26742,8 +26839,7 @@
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
"depd": {
"version": "2.0.0",
@ -28331,8 +28427,7 @@
"follow-redirects": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
"dev": true
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
},
"forever-agent": {
"version": "0.6.1",
@ -32760,6 +32855,11 @@
"is-wsl": "^2.2.0"
}
},
"opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A=="
},
"optionator": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "react-starter-kit",
"version": "0.4.0",
"version": "0.5.0",
"description": "A modern, create-react-app-based, starter kit for React projects",
"keywords": [
"react",
@ -45,6 +45,7 @@
"*.{ts,js,jsx,tsx,css,scss,json,md}": "prettier --write"
},
"dependencies": {
"@axa-fr/react-oidc": "^6.0.0-beta10",
"@reduxjs/toolkit": "^1.8.3",
"deepmerge": "^4.2.2",
"i18next-http-backend": "^1.4.1",
@ -95,6 +96,7 @@
"file-loader": "^6.2.0",
"fs-extra": "^10.1.0",
"html-webpack-plugin": "^5.5.0",
"http-proxy-middleware": "^2.0.6",
"husky": "^8.0.1",
"i18next": "^21.8.14",
"i18next-browser-languagedetector": "^6.1.4",

View File

@ -1,6 +1,24 @@
const defaultConfig = {
version: "0.1.0",
name: "React-starter-kit",
// oidc: {
// url: "private_url",
// realm: "inforit",
// clientId: "react-base",
// scope: "openid profile email",
// redirectUri: "http://localhost:3000/authentication/callback",
// silentRedirectUri: "http://localhost:3000/authentication/silent-callback",
// serviceWorkerOnly: false,
// },
oidc: {
url: "https://sso.mastermindzh.tech/realms/public-tests",
realm: "public-tests",
clientId: "react-starter-kit",
scope: "openid profile email",
redirectUri: "http://localhost:3000/authentication/callback",
silentRedirectUri: "http://localhost:3000/authentication/silent-callback",
serviceWorkerOnly: false,
},
};
// ignore this :)

View File

@ -10,7 +10,8 @@
"nav": {
"home": "home",
"about": "about",
"counter": "counter"
"counter": "counter",
"tenders": "tenders (with auth)"
},
"about": {
"title": "About"

View File

@ -10,7 +10,8 @@
"nav": {
"home": "home",
"about": "over ons",
"counter": "teller"
"counter": "teller",
"tenders": "aanbestedingen (met auth)"
},
"about": {
"title": "Over ons"

View File

@ -1,26 +1,13 @@
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { AboutContainer } from "./features/about/About";
import { CounterContainer } from "./features/counter/Counter";
import { HomeContainer } from "./features/home/Home";
import { BrowserRouter } from "react-router-dom";
import { Navbar } from "./infrastructure/navbar/Navbar";
import { AppRoutes } from "./Routes";
function App() {
return (
<BrowserRouter>
<div className="App">
<Navbar />
<Routes>
<Route path="/" element={<HomeContainer />} />
<Route path="/about" element={<AboutContainer />} />
<Route path="/counter" element={<CounterContainer />} />
{/* <Route index element={<Home />} /> */}
{/* <Route path="teams" element={<Teams />}>
<Route path=":teamId" element={<Team />} />
<Route path="new" element={<NewTeamForm />} />
<Route index element={<LeagueStandings />} />
</Route> */}
{/* </Route> */}
</Routes>
<AppRoutes />
</div>
</BrowserRouter>
);

42
src/Routes.tsx Normal file
View File

@ -0,0 +1,42 @@
import { OidcSecure } from "@axa-fr/react-oidc";
import { FunctionComponent } from "react";
import { Route, Routes } from "react-router-dom";
import { AboutContainer } from "./features/about/About";
import { CounterContainer } from "./features/examples/counter/Counter";
import { Tenders } from "./features/examples/tenders/Tenders";
import { HomeContainer } from "./features/home/Home";
type Props = {};
export const ROUTE_KEYS = {
home: "",
about: "about",
counter: "counter",
tenders: "tenders",
};
export const AppRoutes: FunctionComponent<Props> = () => {
const { home, about, counter, tenders } = ROUTE_KEYS;
return (
<Routes>
<Route path={home} element={<HomeContainer />} />
<Route path={about} element={<AboutContainer />} />
<Route path={counter} element={<CounterContainer />} />
{/* a route with authentication */}
<Route
path={tenders}
element={
<OidcSecure>
<Tenders />
</OidcSecure>
}
/>
{/* <Route index element={<Home />} /> */}
{/* <Route path="teams" element={<Teams />}>
<Route path=":teamId" element={<Team />} />
<Route path="new" element={<NewTeamForm />} />
<Route index element={<LeagueStandings />} />
</Route> */}
{/* </Route> */}
</Routes>
);
};

View File

@ -1,5 +1,5 @@
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
import counterReducer from "../features/counter/state/counterSlice";
import counterReducer from "../features/examples/counter/state/counterSlice";
export const store = configureStore({
reducer: {

View File

@ -2,7 +2,14 @@ import { FunctionComponent, ReactNode } from "react";
import { I18nextProvider, useTranslation } from "react-i18next";
import i18n from "./i18n";
type Props = { children?: ReactNode; keysOnly?: boolean };
type Props = {
children?: ReactNode;
/**
* Whether to show the translation keys instead of translated text
* Can be useful to test languages that don't have full translations
*/
keysOnly?: boolean;
};
const ProvidedComponent: FunctionComponent<Props> = ({ children, keysOnly }) => {
const [_translate, i18nSettings] = useTranslation();

View File

@ -1,4 +0,0 @@
// A mock function to mimic making an async request for data
export function fetchCount(amount = 1) {
return new Promise<{ data: number }>((resolve) => setTimeout(() => resolve({ data: amount }), 500));
}

View File

@ -18,7 +18,7 @@
padding-left: 16px;
padding-right: 16px;
margin-top: 2px;
font-family: 'Courier New', Courier, monospace;
font-family: "Courier New", Courier, monospace;
}
.button {
@ -60,7 +60,7 @@
}
.asyncButton:after {
content: '';
content: "";
background-color: rgba(112, 76, 182, 0.15);
display: block;
position: absolute;

View File

@ -1,7 +1,7 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { useAppDispatch, useAppSelector } from "../../../app/hooks";
import styles from "./Counter.module.css";
import { incrementAsync } from "./state/actions/incrementAsync";
import {

View File

@ -0,0 +1,6 @@
// A mock function to mimic making an async request for data
export function fetchCount(amount = 1) {
return new Promise<{ data: number }>((resolve) =>
setTimeout(() => resolve({ data: amount }), 500),
);
}

View File

@ -1,8 +1,4 @@
import counterReducer, {
increment,
decrement,
incrementByAmount,
} from "./counterSlice";
import counterReducer, { increment, decrement, incrementByAmount } from "./counterSlice";
import { CounterState } from "../models/CounterState";
describe("counter reducer", () => {

View File

@ -1,5 +1,5 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { AppThunk, RootState } from "../../../app/store";
import { AppThunk, RootState } from "../../../../app/store";
import { CounterState } from "../models/CounterState";
import { incrementAsync } from "./actions/incrementAsync";

View File

@ -0,0 +1,33 @@
import { useOidcAccessToken } from "@axa-fr/react-oidc";
import { FunctionComponent, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
type Props = {};
export const Tenders: FunctionComponent<Props> = () => {
const [tenders, setTenders] = useState(null);
const [translate] = useTranslation();
const { accessToken } = useOidcAccessToken();
useEffect(() => {
// this uses the CORS proxy in our webpack setup
// tendersguru is mapped to "https://tenders.guru" in setupProxy.js
// it will strip the tendersguru part, so the full url will be: https://tenders.guru/api/ro/tenders
fetch("tendersguru/api/ro/tenders", {
// fetch's way of adding headers. Not required to access the api... but :shrug:
headers: new Headers({
Authorization: `Bearer ${accessToken}`,
}),
})
.then((response) => response.json())
.then((data) => {
setTenders(data);
});
}, [accessToken]);
return (
<>
<h1>{translate("nav.tenders")}</h1>
{tenders ? <pre>{JSON.stringify(tenders, null, 2)}</pre> : <h2>loading...</h2>}
</>
);
};

View File

@ -8,19 +8,39 @@ export const HomeContainer: FunctionComponent<Props> = () => {
<p>This is the react base app :)</p>
<span>
<span>Learn </span>
<a className="App-link" href="https://reactjs.org/" target="_blank" rel="noopener noreferrer">
<a
className="App-link"
href="https://reactjs.org/"
target="_blank"
rel="noopener noreferrer"
>
React
</a>
<span>, </span>
<a className="App-link" href="https://redux.js.org/" target="_blank" rel="noopener noreferrer">
<a
className="App-link"
href="https://redux.js.org/"
target="_blank"
rel="noopener noreferrer"
>
Redux
</a>
<span>, </span>
<a className="App-link" href="https://redux-toolkit.js.org/" target="_blank" rel="noopener noreferrer">
<a
className="App-link"
href="https://redux-toolkit.js.org/"
target="_blank"
rel="noopener noreferrer"
>
Redux Toolkit
</a>
,<span> and </span>
<a className="App-link" href="https://react-redux.js.org/" target="_blank" rel="noopener noreferrer">
<a
className="App-link"
href="https://react-redux.js.org/"
target="_blank"
rel="noopener noreferrer"
>
React Redux
</a>
</span>

View File

@ -5,6 +5,7 @@ import App from "./App";
import { store } from "./app/store";
import "./index.css";
import "./infrastructure/i18n/init";
import { OidcProvider } from "./infrastructure/sso/OidcProvider";
import { Loader } from "./infrastructure/wrappers/WithPageSuspense";
import reportWebVitals from "./reportWebVitals";
@ -14,9 +15,11 @@ const root = createRoot(container);
root.render(
<React.StrictMode>
<Provider store={store}>
<Loader>
<App />
</Loader>
<OidcProvider>
<Loader>
<App />
</Loader>
</OidcProvider>
</Provider>
</React.StrictMode>,
);

View File

@ -1,4 +1,11 @@
import { OIDCConfig } from "../sso/models/OIDCConfig";
export interface RunTimeConfig {
version: number;
name: string;
/**
* Settings for the OIDC connection
*/
oidc: OIDCConfig;
}

View File

@ -1,4 +1,5 @@
import deepmerge from "deepmerge";
import { RunTimeConfig } from "./RunTimeConfig";
/**
* gets and merges both the regular config and the override config from the window
@ -12,4 +13,4 @@ export const mergeConfigs = () => {
mergeConfigs();
export const Config = window.mergedConfig;
export const Config: RunTimeConfig = window.mergedConfig;

View File

@ -1,18 +1,25 @@
import { render, screen } from "@testing-library/react";
import { render, screen, waitFor } from "@testing-library/react";
import { WithTestTranslations } from "../../app/tests/mocks/i18n/WithTestTranslations";
import { OidcProvider } from "../sso/OidcProvider";
import { WithRouter } from "../wrappers/WithRouter";
import { Navbar } from "./Navbar";
describe("Navbar container", () => {
it.only("renders a navigation section identified by the nav test-id", () => {
it.only("renders a navigation section identified by the nav test-id", async () => {
render(
<WithTestTranslations>
<WithRouter>
<Navbar />
</WithRouter>
{/* for simple tests where we don't need auth we don't actually have to mock responses */}
<OidcProvider>
<WithRouter>
<Navbar />
</WithRouter>
</OidcProvider>
</WithTestTranslations>,
);
expect(screen.getAllByTestId("nav")?.length).toBeGreaterThan(0);
// because of the extra loaders we wait for a result just to be sure
// see: https://testing-library.com/docs/guide-disappearance/
await waitFor(() => {
expect(screen.getAllByTestId("nav")?.length).toBeGreaterThan(0);
});
});
});

View File

@ -1,13 +1,18 @@
import { useOidc, useOidcAccessToken } from "@axa-fr/react-oidc";
import { DateTime } from "luxon";
import { FunctionComponent } from "react";
import { Trans, useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { ROUTE_KEYS } from "../../Routes";
import { Config } from "../config";
import "./Navbar.css";
type Props = {};
export const Navbar: FunctionComponent<Props> = () => {
const [translate, i18n] = useTranslation();
const { login, logout, isAuthenticated } = useOidc();
const { accessTokenPayload } = useOidcAccessToken();
const { home, about, counter } = ROUTE_KEYS;
return (
<>
<h1>{translate("navBar.intro")}</h1>
@ -15,20 +20,30 @@ export const Navbar: FunctionComponent<Props> = () => {
{/* trans can also be used to translate */}
{Config.name} <Trans i18nKey="navBar.version">version:</Trans>
{JSON.stringify(Config.version)}
<button
onClick={() => {
isAuthenticated ? logout() : login("/");
}}
>
{isAuthenticated ? `logout (${accessTokenPayload.email})` : "login"}
</button>
</p>
{/* This translation uses a formatter in the translation files */}
<p>{translate("navBar.currentDate", { date: DateTime.now().toJSDate() })}</p>
<nav data-testid="nav">
<Link to="/" data-testid="nav.home">
<Link to={home} data-testid="nav.home">
{translate("nav.home")}
</Link>
<Link to="/about" data-testid="nav.about">
<Link to={about} data-testid="nav.about">
{translate("nav.about")}
</Link>
<Link to="/counter" data-testid="nav.counter">
<Link to={counter} data-testid="nav.counter">
{translate("nav.counter")}
</Link>
<Link to="/tenders" data-testid="nav.tenders">
{translate("nav.tenders")}
</Link>
<button onClick={() => i18n.changeLanguage("en")}>en</button>
<button onClick={() => i18n.changeLanguage("nl")}>nl</button>
<hr />

View File

@ -0,0 +1,14 @@
import { OidcConfiguration } from "@axa-fr/react-oidc/dist/vanilla/oidc";
import { Config } from "../config";
const { clientId, redirectUri, silentRedirectUri, scope, url } = Config.oidc;
/* eslint-disable camelcase */
export const SSOConfiguration: OidcConfiguration = {
client_id: clientId,
redirect_uri: redirectUri,
silent_redirect_uri: silentRedirectUri,
scope,
authority: url,
service_worker_only: false,
};

View File

@ -0,0 +1,28 @@
import { OidcProvider as ReactOidcProvider } from "@axa-fr/react-oidc";
import { FunctionComponent, ReactNode } from "react";
import { AppLoader } from "../loader/appLoader";
import { SSOConfiguration } from "./Configuration";
import { Authenticating } from "./overrides/Authenticating";
import { AuthenticatingError } from "./overrides/AuthenticatingError";
import { CallBackSuccess } from "./overrides/CallBackSuccess";
import { ServiceWorkerNotSupported } from "./overrides/ServiceWorkerNotSupported";
import { SessionLost } from "./overrides/SessionLost";
type Props = { children?: ReactNode; [x: string]: any };
export const OidcProvider: FunctionComponent<Props> = ({ children, ...rest }) => {
return (
<ReactOidcProvider
loadingComponent={AppLoader}
authenticatingErrorComponent={AuthenticatingError}
authenticatingComponent={Authenticating}
sessionLostComponent={SessionLost}
serviceWorkerNotSupportedComponent={ServiceWorkerNotSupported}
callbackSuccessComponent={CallBackSuccess}
configuration={SSOConfiguration}
{...rest}
>
{children}
</ReactOidcProvider>
);
};

View File

@ -0,0 +1,31 @@
export interface OIDCConfig {
/**
* Authority URL
*/
url: string;
/**
* Realm to authenticate against
*/
realm: string;
/**
* Id of this client
*/
clientId: string;
/**
* Scope(s) that you want to request
*/
scope: string;
/**
* URI to redirect to after successful login
*/
redirectUri: string;
/**
* redirect uri for the silent refresh
*/
silentRedirectUri: string;
}

View File

@ -0,0 +1,3 @@
export interface SSOResult {
configurationName: string;
}

View File

@ -0,0 +1,9 @@
import { FunctionComponent } from "react";
import { SSOResult } from "../models/SSOResult";
export const Authenticating: FunctionComponent<SSOResult> = ({ configurationName }) => (
<>
<h1>Authentication in progress for {configurationName}</h1>
<p>You will be redirected to the login page.</p>
</>
);

View File

@ -0,0 +1,9 @@
import { FunctionComponent } from "react";
import { SSOResult } from "../models/SSOResult";
export const AuthenticatingError: FunctionComponent<SSOResult> = ({ configurationName }) => (
<>
<h1>Error for {configurationName}</h1>
<p>An error occurred during authentication.</p>
</>
);

View File

@ -0,0 +1,9 @@
import { FunctionComponent } from "react";
import { SSOResult } from "../models/SSOResult";
export const CallBackSuccess: FunctionComponent<SSOResult> = ({ configurationName }) => (
<>
<h1>Authentication complete for {configurationName}</h1>
<p>You will be redirected...</p>
</>
);

View File

@ -0,0 +1,9 @@
import { FunctionComponent } from "react";
import { SSOResult } from "../models/SSOResult";
export const ServiceWorkerNotSupported: FunctionComponent<SSOResult> = ({ configurationName }) => (
<>
<h1>Unable to authenticate on this browser for {configurationName}</h1>
<p>Your browser is not configured to support Service Workers.</p>
</>
);

View File

@ -0,0 +1,17 @@
import { useOidc } from "@axa-fr/react-oidc";
import { FunctionComponent } from "react";
import { SSOResult } from "../models/SSOResult";
export const SessionLost: FunctionComponent<SSOResult> = ({ configurationName }) => {
const { login } = useOidc(configurationName);
return (
<>
<h1>Session timed out for {configurationName}</h1>
<p>Your session has expired. Please re-authenticate.</p>
<button type="button" onClick={() => login("/")}>
Login
</button>
</>
);
};

28
src/setupProxy.js Normal file
View File

@ -0,0 +1,28 @@
const { createProxyMiddleware } = require("http-proxy-middleware");
/**
* Create a simple proxy that automatically removes the prefix endpoint
* @param {*} app app to configure proxy on
* @param {*} endpoint endpoint you want to use for the proxy
* @param {*} target proxy target
*/
const createSimpleProxy = (app, endpoint, target) => {
app.use(
endpoint,
createProxyMiddleware({
target,
changeOrigin: true,
pathRewrite: {
[`^${endpoint}`]: "",
},
}),
);
};
/**
* Actual proxy configuration
* @param {*} app app to configure proxy on
*/
module.exports = function (app) {
createSimpleProxy(app, "/tendersguru", "https://tenders.guru");
};