From 54ccbb5f7c31e4a7820a4f241905ccd99b2a4dcf Mon Sep 17 00:00:00 2001 From: xaralis <filip.varecha@fragaria.cz> Date: Tue, 8 Dec 2020 19:41:46 +0100 Subject: [PATCH] Initial login flow --- .gitignore | 1 + package-lock.json | 59 +++++++++++++++++++++++++- package.json | 5 ++- src/App.jsx | 83 ++++++++++++++++++++++++++++--------- src/App.test.js | 9 ++-- src/components/Button.jsx | 36 ++++++++++++++++ src/components/Footer.jsx | 65 +---------------------------- src/components/HomeHero.jsx | 41 ------------------ src/components/Navbar.jsx | 62 +++++++++++++++++---------- src/config.js | 2 +- src/index.css | 8 ---- src/index.js | 12 +++--- src/keycloak.js | 15 +++++++ src/pages/ForPress.jsx | 11 ----- src/pages/ForVisitors.jsx | 11 ----- src/pages/Home.jsx | 18 +------- src/pages/Program.jsx | 6 +-- src/serviceWorker.js | 44 ++++++++++---------- src/setupTests.js | 2 +- src/stores.js | 10 +++++ 20 files changed, 267 insertions(+), 233 deletions(-) create mode 100644 src/components/Button.jsx delete mode 100644 src/components/HomeHero.jsx delete mode 100644 src/index.css create mode 100644 src/keycloak.js delete mode 100644 src/pages/ForPress.jsx delete mode 100644 src/pages/ForVisitors.jsx create mode 100644 src/stores.js diff --git a/.gitignore b/.gitignore index 4d29575..00ec607 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ .env.development.local .env.test.local .env.production.local +.eslintcache npm-debug.log* yarn-debug.log* diff --git a/package-lock.json b/package-lock.json index 19374d9..6006e7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1400,6 +1400,27 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" }, + "@react-keycloak/core": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@react-keycloak/core/-/core-2.2.1.tgz", + "integrity": "sha512-MKoawiLMamhCrcEQ79svZZV8lw+ojkcz4LMGeCzHULlZB8sTDY3uBg7S8NxhpdPdyAjQoQ5ExKL4Il75OBIamg==", + "requires": { + "@babel/runtime": "^7.9.0", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.0.1" + } + }, + "@react-keycloak/web": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@react-keycloak/web/-/web-2.1.4.tgz", + "integrity": "sha512-HZry6/UIeStGc2reqYhRFAkmVGCb4fQfOdOK2bJ6RCatN5uuJ+sDu5w5L97JtGNirTqP2eKsTUM3Dx1EvgJ4ng==", + "requires": { + "@babel/runtime": "^7.9.0", + "@react-keycloak/core": "^2.2.1", + "hoist-non-react-statics": "^3.3.2", + "prop-types": "^15.7.2" + } + }, "@sentry/browser": { "version": "5.23.0", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.23.0.tgz", @@ -1716,7 +1737,8 @@ "@testing-library/user-event": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-7.2.1.tgz", - "integrity": "sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA==" + "integrity": "sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA==", + "dev": true }, "@types/babel__core": { "version": "7.1.9", @@ -8192,6 +8214,11 @@ } } }, + "js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8340,6 +8367,15 @@ "object.assign": "^4.1.0" } }, + "keycloak-js": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-10.0.2.tgz", + "integrity": "sha512-7nkg4Ob1khHGcNbuK36AMndKUEuIQFpNlWU9ygWs7nSBPCI9VZ8dJjjXfKJHm0ewgcqLFGPIJ6bxxRlfcQ6sLg==", + "requires": { + "base64-js": "1.3.1", + "js-sha256": "0.9.0" + } + }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", @@ -10948,6 +10984,22 @@ } } }, + "pullstate": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/pullstate/-/pullstate-1.20.4.tgz", + "integrity": "sha512-SksJ70iYNrC+YsGjMx54pXT2/iYYUu3zg9hezjHNjPPwyViglo4lh+N0I4DwB6xw92s+9NagD3AXMGMfiIt76g==", + "requires": { + "fast-deep-equal": "^3.1.3", + "immer": "^7.0.1" + }, + "dependencies": { + "immer": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/immer/-/immer-7.0.15.tgz", + "integrity": "sha512-yM7jo9+hvYgvdCQdqvhCNRRio0SCXc8xDPzA25SvKWa7b1WVPjLwQs1VYU5JPXjcJPTqAa5NP5dqpORGYBQ2AA==" + } + } + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -11313,6 +11365,11 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz", "integrity": "sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==" }, + "react-fast-compare": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", + "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index 3e35dae..f64eb0b 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,11 @@ "version": "0.1.0", "private": true, "dependencies": { + "@react-keycloak/web": "^2.1.4", "@sentry/react": "^5.23.0", - "@testing-library/user-event": "^7.2.1", "classnames": "^2.2.6", + "keycloak-js": "^10.0.2", + "pullstate": "^1.20.4", "react": "^16.13.1", "react-device-detect": "^1.13.1", "react-dom": "^16.13.1", @@ -94,6 +96,7 @@ }, "devDependencies": { "@testing-library/jest-dom": "^4.2.4", + "@testing-library/user-event": "^7.2.1", "@testing-library/react": "^9.5.0", "babel-core": "^6.26.3", "babel-eslint": "^10.1.0", diff --git a/src/App.jsx b/src/App.jsx index c523e4f..481d080 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,19 +1,16 @@ -import React from "react"; -import { - BrowserRouter as Router, - Route, - Switch, -} from "react-router-dom"; +import React, { useCallback, useEffect } from "react"; +import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; +import { KeycloakProvider } from "@react-keycloak/web"; import * as Sentry from "@sentry/react"; +import { AuthStore } from "stores"; import Footer from "components/Footer"; import Navbar from "components/Navbar"; - import Home from "pages/Home"; -import ForVisitors from "pages/ForVisitors"; -import ForPress from "pages/ForPress"; import Program from "pages/Program"; +import keycloak from "./keycloak"; + /** * If configured, set up Sentry client that reports uncaught errors down to * https://sentry.io. @@ -25,19 +22,65 @@ if (process.env.REACT_APP_SENTRY_DSN) { }); } +const onKeycloakEvent = (event) => { + if (["onAuthRefreshSuccess", "onAuthSuccess"].includes(event)) { + Sentry.setUser(keycloak.tokenParsed); + AuthStore.update((state) => { + state.isAuthenticated = true; + state.user = { + name: keycloak.tokenParsed.name, + groups: keycloak.tokenParsed.groups, + }; + }); + } +}; + +const LoadingComponent = <p className="block mt-4 font-bold">NaÄŤĂtánĂ ...</p>; + const BaseApp = () => { + const loadGroupMappings = useCallback(async () => { + const resp = await fetch("https://iapi.pirati.cz/v1/groups"); + const mappings = await resp.json(); + + AuthStore.update((state) => { + state.groupMappings = mappings; + }); + }, []); + + useEffect(() => { + loadGroupMappings(); + }, [loadGroupMappings]); + + return ( + <Router> + <Navbar /> + <Switch> + <Route exact path="/" children={<Home />} /> + <Route exact path="/program" children={<Program />} /> + </Switch> + <Footer /> + </Router> + ); +}; + +const AuthenticatedApp = () => { + const keycloakInitConfig = { + onLoad: "check-sso", + // Necessary to prevent Keycloak cookie issues: + // @see: https://stackoverflow.com/a/63588334/303184 + checkLoginIframe: false, + }; + return ( <> - <Router> - <Navbar /> - <Switch> - <Route exact path="/" children={<Home />} /> - <Route exact path="/program" children={<Program />} /> - <Route exact path="/pro-ucastniky" children={<ForVisitors />} /> - <Route exact path="/media" children={<ForPress />} /> - </Switch> - <Footer /> - </Router> + <KeycloakProvider + keycloak={keycloak} + initConfig={keycloakInitConfig} + LoadingComponent={LoadingComponent} + onEvent={onKeycloakEvent} + > + <BaseApp /> + </KeycloakProvider> </> ); }; @@ -61,7 +104,7 @@ const ErrorBoundaryFallback = () => ( const App = Sentry.withProfiler(() => { return ( <Sentry.ErrorBoundary fallback={ErrorBoundaryFallback} showDialog> - <BaseApp /> + <AuthenticatedApp /> </Sentry.ErrorBoundary> ); }); diff --git a/src/App.test.js b/src/App.test.js index 4db7ebc..80af77c 100644 --- a/src/App.test.js +++ b/src/App.test.js @@ -1,8 +1,9 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import App from './App'; +import React from "react"; +import { render } from "@testing-library/react"; -test('renders learn react link', () => { +import App from "./App"; + +test("renders learn react link", () => { const { getByText } = render(<App />); const linkElement = getByText(/learn react/i); expect(linkElement).toBeInTheDocument(); diff --git a/src/components/Button.jsx b/src/components/Button.jsx new file mode 100644 index 0000000..9527911 --- /dev/null +++ b/src/components/Button.jsx @@ -0,0 +1,36 @@ +import React from "react"; +import classNames from "classnames"; + +const Button = ({ + className, + icon, + hoverActive = true, + fullwidth = false, + children, + ...props +}) => { + const btnClass = classNames( + "btn", + { + "btn--icon": !!icon, + "btn--hoveractive": hoverActive, + "btn--fullwidth md:btn--autowidth": fullwidth, + }, + className + ); + + return ( + <button className={btnClass} {...props}> + <div className="btn__body-wrap"> + <div className="btn__body ">{children}</div> + {!!icon && ( + <div className="btn__icon"> + <i className={icon}></i> + </div> + )} + </div> + </button> + ); +}; + +export default Button; diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx index e98f14c..8034cf3 100644 --- a/src/components/Footer.jsx +++ b/src/components/Footer.jsx @@ -15,68 +15,7 @@ const Footer = () => { sdĂlet za stejnĂ˝ch podmĂnek. </p> </section> - <section className="footer__main-links bg-grey-700 text-white lg:grid grid-cols-3 gap-4"> - <div className="pt-4 lg:py-0 border-t border-grey-400 lg:border-t-0"> - <div className="footer-collapsible"> - <span className="text-xl uppercase text-white footer-collapsible__toggle"> - Program - </span>{" "} - <div className=""> - <ul className="mt-6 space-y-2 text-grey-200"> - <li> - <a href="#">Pátek</a> - </li>{" "} - <li> - <a href="#">Sobota</a> - </li>{" "} - <li> - <a href="#">NedÄ›le</a> - </li> - </ul> - </div> - </div> - </div> - <div className="pt-8 pb-4 lg:py-0"> - <div className="footer-collapsible"> - <span className="text-xl uppercase text-white footer-collapsible__toggle"> - Pro účastnĂky - </span>{" "} - <div className=""> - <ul className="mt-6 space-y-2 text-grey-200"> - <li> - <a href="#">Registrace účastnĂkĹŻ</a> - </li>{" "} - <li> - <a href="#">Kudy na fĂłrum</a> - </li> - <li> - <a href="#">UbytovánĂ</a> - </li> - </ul> - </div> - </div> - </div>{" "} - <div className="py-4 lg:py-0 border-t border-grey-400 lg:border-t-0"> - <div className="footer-collapsible"> - <span className="text-xl uppercase text-white footer-collapsible__toggle"> - Pro mĂ©dia - </span>{" "} - <div className=""> - <ul className="mt-6 space-y-2 text-grey-200"> - <li> - <a href="#">Registrace novinářů</a> - </li>{" "} - <li> - <a href="#">Informace pro novináře</a> - </li>{" "} - <li> - <a href="#">Presskit</a> - </li> - </ul> - </div> - </div> - </div>{" "} - </section> + <section className="footer__main-links bg-grey-700 text-white lg:grid grid-cols-3 gap-4"></section> <section className="footer__social lg:text-right"> <div className="flex flex-col md:flex-row lg:flex-col lg:items-end space-y-2 md:space-y-0 md:space-x-2 lg:space-x-0 lg:space-y-2"> <button className="btn btn--icon btn--blue-300 btn--hoveractive text-lg btn--fullwidth sm:btn--autowidth"> @@ -137,7 +76,7 @@ const Footer = () => { <a href="mailto:example@example.com" className="contact-line contact-line--responsive icon-link badge__link" - > + > <i className="ico--envelope"></i> <span>example@example.com</span> </a> diff --git a/src/components/HomeHero.jsx b/src/components/HomeHero.jsx deleted file mode 100644 index d37b92c..0000000 --- a/src/components/HomeHero.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from "react"; -import { NavLink } from "react-router-dom"; - -const HomeHero = () => { - return ( - <article - className="hero py-24" - > - <div className="container container--default text-center"> - <h1 className="head-alt-lg mb-4">12. CelostátnĂ fĂłrum</h1> - <h2 className="head-alt-xl">ÄŚeskĂ© pirátskĂ© strany</h2> - - <p className="head-heavy-base mt-4">11. - 12. ledna, Pardubice</p> - <p className="head-heavy-sm">KulturnĂ centrum IDEON</p> - - - <div className="mt-4 md:mt-8 space-x-4"> - <NavLink className="btn btn--blue-300 btn--icon btn--hoveractive btn--fullwidth md:btn--autowidth text-xl" to="/registrace"> - <div className="btn__body-wrap"> - <div className="btn__body ">Registrace</div> - <div className="btn__icon "> - <i className="ico--book"></i> - </div> - </div> - </NavLink> - - <NavLink className="btn btn--violet-400 btn--icon btn--hoveractive btn--fullwidth md:btn--autowidth text-xl" to="/program"> - <div className="btn__body-wrap"> - <div className="btn__body ">Program</div> - <div className="btn__icon "> - <i className="ico--calendar"></i> - </div> - </div> - </NavLink> - </div> - </div> - </article> - ); -}; - -export default HomeHero; diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index de3062f..838698c 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -1,9 +1,22 @@ -import React, { useState } from "react"; +import React, { useCallback, useState } from "react"; import { isBrowser } from "react-device-detect"; import { NavLink } from "react-router-dom"; +import { useKeycloak } from "@react-keycloak/web"; +import { AuthStore } from "stores"; + +import Button from "components/Button"; const Navbar = () => { const [showMenu, setShowMenu] = useState(isBrowser); + const { keycloak } = useKeycloak(); + const { isAuthenticated, user } = AuthStore.useState(); + + const login = useCallback(() => { + keycloak.login(); + }, [keycloak]); + const logout = useCallback(() => { + keycloak.logout(); + }, [keycloak]); return ( <nav className="navbar navbar--simple"> @@ -13,48 +26,55 @@ const Navbar = () => { <img src={`${process.env.REACT_APP_STYLEGUIDE_URL}/images/logo-round-white.svg`} className="w-8" + alt="Pirátská strana" /> </NavLink> - <NavLink to="/" className="pl-4 font-bold text-xl lg:border-r lg:border-grey-300 lg:pr-8 hover:no-underline"> + <NavLink + to="/" + className="pl-4 font-bold text-xl lg:border-r lg:border-grey-300 lg:pr-8 hover:no-underline" + > CelostátnĂ fĂłrum 2021 </NavLink> </div> <div className="navbar__menutoggle my-4 flex justify-end lg:hidden"> - <a - href="#" + <button onClick={() => setShowMenu(!showMenu)} className="no-underline hover:no-underline" > <i className="ico--menu text-3xl"></i> - </a> + </button> </div> {showMenu && ( <> <div className="navbar__main navbar__section navbar__section--expandable container-padding--zero lg:container-padding--auto"> <ul className="navbar-menu text-white"> <li className="navbar-menu__item"> - <NavLink className="navbar-menu__link" to="/program">Program</NavLink> - </li> - <li className="navbar-menu__item"> - <NavLink className="navbar-menu__link" to="/registrace">Registrace</NavLink> - </li> - <li className="navbar-menu__item"> - <NavLink className="navbar-menu__link" to="/pro-ucastniky">Pro účastnĂky</NavLink> - </li> - <li className="navbar-menu__item"> - <NavLink className="navbar-menu__link" to="/media">Pro mĂ©dia</NavLink> + <NavLink className="navbar-menu__link" to="/program"> + Program + </NavLink> </li> </ul> </div> <div className="navbar__actions navbar__section navbar__section--expandable container-padding--zero lg:container-padding--auto self-start flex flex-col sm:flex-row lg:flex-col sm:space-x-4 space-y-2 sm:space-y-0 lg:space-y-2 xl:flex-row xl:space-x-2 xl:space-y-0"> - <NavLink className="btn btn--white btn--icon btn--hoveractive btn--fullwidth md:btn--autowidth text-sm" to="/program"> - <div className="btn__body-wrap"> - <div className="btn__body ">OsobnĂ zĂłna</div> - <div className="btn__icon"> - <i className="ico--users"></i> + {!isAuthenticated && ( + <Button className="btn--white" onClick={login}> + PĹ™ihlásit se + </Button> + )} + {isAuthenticated && ( + <div className="flex items-center space-x-4"> + <span className="head-heavy-2xs">{user.name}</span> + <div className="avatar avatar--2xs"> + <img + src="http://placeimg.com/100/100/people" + alt="Avatar" + /> </div> + <button onClick={logout}> + <i className="ico--log-out"></i> + </button> </div> - </NavLink> + )} </div> </> )} diff --git a/src/config.js b/src/config.js index 10456bc..3d07b03 100644 --- a/src/config.js +++ b/src/config.js @@ -1,3 +1,3 @@ export default { - styleguideUrl: "https://styleguide.pir-test.eu/latest" + styleguideUrl: "https://styleguide.pir-test.eu/latest", }; diff --git a/src/index.css b/src/index.css deleted file mode 100644 index a09551d..0000000 --- a/src/index.css +++ /dev/null @@ -1,8 +0,0 @@ -.h-screen { - min-height: 100vh; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/src/index.js b/src/index.js index f5185c1..dd6b4cf 100644 --- a/src/index.js +++ b/src/index.js @@ -1,14 +1,14 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; -import App from './App'; -import * as serviceWorker from './serviceWorker'; +import React from "react"; +import ReactDOM from "react-dom"; + +import App from "./App"; +import * as serviceWorker from "./serviceWorker"; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, - document.getElementById('root') + document.getElementById("root") ); // If you want your app to work offline and load faster, you can change diff --git a/src/keycloak.js b/src/keycloak.js new file mode 100644 index 0000000..b2aa703 --- /dev/null +++ b/src/keycloak.js @@ -0,0 +1,15 @@ +import Keycloak from "keycloak-js"; + +// Setup Keycloak instance as needed +// Pass initialization options as required or leave blank to load from 'keycloak.json' +const keycloak = Keycloak({ + url: "https://auth.pirati.cz/auth", + realm: "pirati", + clientId: "cf-online", +}); + +// keycloak.init({ +// onLoad: "check-sso", +// }); + +export default keycloak; diff --git a/src/pages/ForPress.jsx b/src/pages/ForPress.jsx deleted file mode 100644 index d2c02ed..0000000 --- a/src/pages/ForPress.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -const ForPress = () => { - return ( - <> - Press - </> - ); -}; - -export default ForPress; diff --git a/src/pages/ForVisitors.jsx b/src/pages/ForVisitors.jsx deleted file mode 100644 index d0f5ddd..0000000 --- a/src/pages/ForVisitors.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -const ForVisitors = () => { - return ( - <> - Visitors - </> - ); -}; - -export default ForVisitors; diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index 92e3636..ab2a061 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -1,25 +1,9 @@ import React from "react"; -import HomeHero from "components/HomeHero"; - const Home = () => { return ( <> - <HomeHero /> - - <hr /> - - <section className="container container--default my-8 text-center"> - <h1 className="head-alt-base mb-4">CelostátnĂ fĂłrum</h1> - <p>CelostátnĂ fĂłrum PirátskĂ© strany je nejvyššĂm orgánem strany a zasedánĂ se podle moĹľnostà účastnĂ kaĹľdĂ˝ ÄŤlen strany.</p> - </section> - - <hr /> - - <section className="container container--default my-8 text-center"> - <h1 className="head-alt-base mb-4">ZákladnĂ informace</h1> - <p>CelostátnĂ fĂłrum PirátskĂ© strany je nejvyššĂm orgánem strany a zasedánĂ se podle moĹľnostà účastnĂ kaĹľdĂ˝ ÄŤlen strany.</p> - </section> + <p>Homepage</p> </> ); }; diff --git a/src/pages/Program.jsx b/src/pages/Program.jsx index 75f5f39..e59111b 100644 --- a/src/pages/Program.jsx +++ b/src/pages/Program.jsx @@ -1,11 +1,7 @@ import React from "react"; const Schedule = () => { - return ( - <> - Schedule - </> - ); + return <>Schedule</>; }; export default Schedule; diff --git a/src/serviceWorker.js b/src/serviceWorker.js index b04b771..c7cd666 100644 --- a/src/serviceWorker.js +++ b/src/serviceWorker.js @@ -11,9 +11,9 @@ // opt-in, read https://bit.ly/CRA-PWA const isLocalhost = Boolean( - window.location.hostname === 'localhost' || + window.location.hostname === "localhost" || // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || + window.location.hostname === "[::1]" || // 127.0.0.0/8 are considered localhost for IPv4. window.location.hostname.match( /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ @@ -21,7 +21,7 @@ const isLocalhost = Boolean( ); export function register(config) { - if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { // The URL constructor is available in all browsers that support SW. const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); if (publicUrl.origin !== window.location.origin) { @@ -31,7 +31,7 @@ export function register(config) { return; } - window.addEventListener('load', () => { + window.addEventListener("load", () => { const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; if (isLocalhost) { @@ -42,8 +42,8 @@ export function register(config) { // service worker/PWA documentation. navigator.serviceWorker.ready.then(() => { console.log( - 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' + "This web app is being served cache-first by a service " + + "worker. To learn more, visit https://bit.ly/CRA-PWA" ); }); } else { @@ -57,21 +57,21 @@ export function register(config) { function registerValidSW(swUrl, config) { navigator.serviceWorker .register(swUrl) - .then(registration => { + .then((registration) => { registration.onupdatefound = () => { const installingWorker = registration.installing; if (installingWorker == null) { return; } installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { + if (installingWorker.state === "installed") { if (navigator.serviceWorker.controller) { // At this point, the updated precached content has been fetched, // but the previous service worker will still serve the older // content until all client tabs are closed. console.log( - 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + "New content is available and will be used when all " + + "tabs for this page are closed. See https://bit.ly/CRA-PWA." ); // Execute callback @@ -82,7 +82,7 @@ function registerValidSW(swUrl, config) { // At this point, everything has been precached. // It's the perfect time to display a // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); + console.log("Content is cached for offline use."); // Execute callback if (config && config.onSuccess) { @@ -93,25 +93,25 @@ function registerValidSW(swUrl, config) { }; }; }) - .catch(error => { - console.error('Error during service worker registration:', error); + .catch((error) => { + console.error("Error during service worker registration:", error); }); } function checkValidServiceWorker(swUrl, config) { // Check if the service worker can be found. If it can't reload the page. fetch(swUrl, { - headers: { 'Service-Worker': 'script' }, + headers: { "Service-Worker": "script" }, }) - .then(response => { + .then((response) => { // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get('content-type'); + const contentType = response.headers.get("content-type"); if ( response.status === 404 || - (contentType != null && contentType.indexOf('javascript') === -1) + (contentType != null && contentType.indexOf("javascript") === -1) ) { // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { + navigator.serviceWorker.ready.then((registration) => { registration.unregister().then(() => { window.location.reload(); }); @@ -123,18 +123,18 @@ function checkValidServiceWorker(swUrl, config) { }) .catch(() => { console.log( - 'No internet connection found. App is running in offline mode.' + "No internet connection found. App is running in offline mode." ); }); } export function unregister() { - if ('serviceWorker' in navigator) { + if ("serviceWorker" in navigator) { navigator.serviceWorker.ready - .then(registration => { + .then((registration) => { registration.unregister(); }) - .catch(error => { + .catch((error) => { console.error(error.message); }); } diff --git a/src/setupTests.js b/src/setupTests.js index 74b1a27..5fdf001 100644 --- a/src/setupTests.js +++ b/src/setupTests.js @@ -2,4 +2,4 @@ // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom/extend-expect'; +import "@testing-library/jest-dom/extend-expect"; diff --git a/src/stores.js b/src/stores.js new file mode 100644 index 0000000..0590a63 --- /dev/null +++ b/src/stores.js @@ -0,0 +1,10 @@ +import { Store } from "pullstate"; + +export const AuthStore = new Store({ + isAuthenticated: false, + user: { + name: null, + groups: null, + }, + groupMappings: null, +}); -- GitLab