App.jsx 5.93 KiB
import React, { Suspense, useEffect } from "react";
import { Helmet, HelmetProvider } from "react-helmet-async";
import ReactHintFactory from "react-hint";
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, refreshAccessToken } 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 ReactHint = ReactHintFactory(React);
const onKeycloakEvent = async (event) => {
if (event === "onTokenExpired") {
console.warn("[auth] access token expired, attempting refresh");
refreshAccessToken();
}
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>
<ReactHint autoPosition events attribute="data-tip" className="tooltip" />
</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}
autoRefreshToken={false}
>
<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;