import React, { Suspense, useEffect } from "react"; import { Helmet, HelmetProvider } from "react-helmet-async"; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import { KeycloakProvider } from "@react-keycloak/web"; import { ExtraErrorData } from "@sentry/integrations/dist/extraerrordata"; import * as Sentry from "@sentry/react"; import { Integrations } from "@sentry/tracing"; import { loadAnnouncements } from "actions/announcements"; import { loadConfig } from "actions/global-info"; import { loadPosts } from "actions/posts"; import { loadProgram } from "actions/program"; import { loadMe } from "actions/users"; import { initializeWSChannel } from "actions/ws"; import Footer from "components/Footer"; import Navbar from "components/Navbar"; import About from "pages/About"; import Home from "pages/Home"; import NotFound from "pages/NotFound"; import Program from "pages/Program"; import Protocol from "pages/Protocol"; import { AuthStore, PostStore } from "stores"; import { updateWindowPosts } from "utils"; import keycloak from "./keycloak"; /** * If configured, set up Sentry client that reports uncaught errors down to * https://sentry.io. */ if (process.env.REACT_APP_SENTRY_DSN) { Sentry.init({ dsn: process.env.REACT_APP_SENTRY_DSN, tracesSampleRate: 0.1, integrations: [new ExtraErrorData(), new Integrations.BrowserTracing()], }); } const onKeycloakEvent = (event) => { if (["onAuthRefreshSuccess", "onAuthSuccess"].includes(event)) { Sentry.setUser(keycloak.tokenParsed); const kcRoles = keycloak.tokenParsed.roles; let role = null; if (kcRoles.includes("chairman")) { role = "chairman"; } else if (kcRoles.includes("member")) { role = "member"; } else { role = "regp"; } AuthStore.update((state) => { state.isAuthenticated = true; state.user = { name: keycloak.tokenParsed.name, username: keycloak.tokenParsed.preferred_username, role, accessToken: keycloak.token, }; }); // Once base user details has been stored, load me details from API. loadMe.run(); PostStore.update((state) => { // Only display proposals verified by chairman to other users. state.filters.showPendingProposals = role === "chairman"; updateWindowPosts(state); }); } }; const LoadingComponent = ( <div className="h-screen w-screen flex justify-center items-center"> <div className="text-center"> <div className="flex flex-col md:flex-row items-center space-x-4 text-center mb-2"> <img src={`${process.env.REACT_APP_STYLEGUIDE_URL}/images/logo-round-black.svg`} className="w-16 mb-2" alt="Pirátská strana" /> <h1 className="head-alt-md md:head-alt-lg">Celostátní fórum 2021</h1> </div> <p className="text-center head-xs md:head-base">Načítám aplikaci ...</p> </div> </div> ); const BaseApp = () => { useEffect(() => { initializeWSChannel.run(); }, []); return ( <HelmetProvider> <Router> <Helmet> <title>CF 2021 | Pirátská strana</title> <meta name="description" content="Oficiální stránka letošního ročníku on-line zasedání Celostátního fóra České pirátské strany, 9. 1. 2021." /> <meta property="og:title" content="CF 2021 | Pirátská strana" /> <meta property="og:description" content="Oficiální stránka letošního ročníku on-line zasedání Celostátního fóra České pirátské strany, 9. 1. 2021." /> </Helmet> <Navbar /> <Switch> <Route exact path="/" children={<Home />} /> <Route exact path="/program" children={<Program />} /> <Route exact path="/protocol" children={<Protocol />} /> <Route exact path="/about" children={<About />} /> <Route component={NotFound} /> </Switch> <Footer /> </Router> </HelmetProvider> ); }; const ConfiguredApp = () => { loadConfig.read(); loadProgram.read(); loadAnnouncements.read(); loadPosts.read(); return ( <Suspense fallback={LoadingComponent}> <BaseApp /> </Suspense> ); }; const AuthenticatedApp = () => { const keycloakInitConfig = { onLoad: "check-sso", // Necessary to prevent Keycloak cookie issues: // @see: https://stackoverflow.com/a/63588334/303184 checkLoginIframe: false, }; return ( <> <KeycloakProvider keycloak={keycloak} initConfig={keycloakInitConfig} LoadingComponent={LoadingComponent} onEvent={onKeycloakEvent} > <Suspense fallback={LoadingComponent}> <ConfiguredApp /> </Suspense> </KeycloakProvider> </> ); }; const ErrorBoundaryFallback = ({ error }) => { return ( <div className="h-screen w-screen flex justify-center items-center"> <div className="text-center"> <h1 className="head-alt-xl text-red-600 mb-4"> V aplikaci došlo k chybě :( </h1> <p className="text-lg leading-normal"> Naši vývojáři o tom již byli informování a opraví to co nejdříve. <br /> Omlouváme se za tuto nepříjemnost. </p> <a href="/" className="btn mt-8"> <div className="btn__body">Načíst znovu</div> </a> </div> </div> ); }; const App = Sentry.withProfiler(() => { return ( <Sentry.ErrorBoundary fallback={ErrorBoundaryFallback} showDialog> <AuthenticatedApp /> </Sentry.ErrorBoundary> ); }); export default App;