diff --git a/package-lock.json b/package-lock.json index 483e11c9f9cfc3c2029fc9e461982d74352f3a54..b5a27f1721dbf97bfa9673577b4877d018d09869 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14556,6 +14556,17 @@ } } }, + "react-helmet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", + "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", + "requires": { + "object-assign": "^4.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.1.1", + "react-side-effect": "^2.1.0" + } + }, "react-intersection-observer": { "version": "8.31.0", "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-8.31.0.tgz", @@ -15017,6 +15028,11 @@ } } }, + "react-side-effect": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.1.tgz", + "integrity": "sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ==" + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", diff --git a/package.json b/package.json index e344b74b4cbb6446dd5aaade8f2c8fd01224159b..5da46dfc97d04752796c470fad7c0d333269c8e6 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "pullstate": "^1.20.5", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-helmet": "^6.1.0", "react-intersection-observer": "^8.31.0", "react-joyride": "^2.3.0", "react-mde": "^11.0.0", diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index bcd5dfd67cd0361b78123e95c2dd96031f27f743..0000000000000000000000000000000000000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/public/img/og.png b/public/img/og.png new file mode 100644 index 0000000000000000000000000000000000000000..63771e5a7661f12728b76f0aa3ddd32499a54d48 Binary files /dev/null and b/public/img/og.png differ diff --git a/public/index.html b/public/index.html index 3fae09c5ba43267bd5a7b910f459ee18ee6d0e08..6e3d8c09dcbc110706d5b29683f38265aadcd2a3 100644 --- a/public/index.html +++ b/public/index.html @@ -17,6 +17,11 @@ <meta property="og:url" content="https://cf2021.pirati.cz/" /> <meta property="og:type" content="website" /> <meta property="og:title" content="CF 2021" /> + <meta property="og:image" content="/img/og.png" /> + <meta property="og:description" content="OficiálnĂ stránka letošnĂho roÄŤnĂku CelostátnĂho fĂłra ÄŚeskĂ© pirátskĂ© strany." /> + <meta name="description" content="OficiálnĂ stránka letošnĂho roÄŤnĂku CelostátnĂho fĂłra ÄŚeskĂ© pirátskĂ© strany." /> + + <title>CF 2021 | Pirátská strana</title> <!-- manifest.json provides metadata used when your web app is installed on a user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ @@ -32,7 +37,6 @@ Learn how to configure a non-root public URL by running `npm run build`. --> <link rel="stylesheet" href="%REACT_APP_STYLEGUIDE_URL%/css/styles.css" /> - <title>CF 2021 | Pirátská strana</title> <script src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver"></script> </head> <body class="h-screen"> diff --git a/public/logo192.png b/public/logo192.png deleted file mode 100644 index fc44b0a3796c0e0a64c3d858ca038bd4570465d9..0000000000000000000000000000000000000000 Binary files a/public/logo192.png and /dev/null differ diff --git a/public/logo512.png b/public/logo512.png deleted file mode 100644 index a4e47a6545bc15971f8f63fba70e4013df88a664..0000000000000000000000000000000000000000 Binary files a/public/logo512.png and /dev/null differ diff --git a/public/manifest.json b/public/manifest.json index 080d6c77ac21bb2ef88a6992b2b73ad93daaca92..c332442ea6af45bc2030b416f89147067de290dd 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,21 +1,21 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "CF2021", + "name": "CelostátnĂ fĂłrum 2021", "icons": [ { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", + "src": "https://styleguide.pir-test.eu/latest/images/favicons/favicon-32x32.png", + "sizes": "32x32 24x24 16x16", "type": "image/x-icon" }, { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" + "src": "https://styleguide.pir-test.eu/latest/images/favicons/favicon-96x96.png", + "sizes": "96x96 64x64", + "type": "image/x-icon" }, { - "src": "logo512.png", + "src": "https://styleguide.pir-test.eu/latest/images/favicons/favicon-196x196.png", "type": "image/png", - "sizes": "512x512" + "sizes": "192x192 196x196" } ], "start_url": ".", diff --git a/src/App.jsx b/src/App.jsx index 1ba3568f3ac2b6bcf3ed6f744f3e315ee7f891f0..0ceedc5779d84bc3c0af762aadde9adc5be5b4af 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,5 @@ import React, { Suspense } from "react"; +import { Helmet } from "react-helmet"; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import { KeycloakProvider } from "@react-keycloak/web"; import * as Sentry from "@sentry/react"; @@ -6,10 +7,10 @@ import * as Sentry from "@sentry/react"; import { loadConfig } from "actions/global-info"; import { loadMe } from "actions/users"; import { initializeWSChannel } from "actions/ws"; -import Button from "components/Button"; import Footer from "components/Footer"; import Navbar from "components/Navbar"; 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"; @@ -80,27 +81,23 @@ const LoadingComponent = ( </div> ); -const NotFound = () => ( - <article className="container container--default py-8 lg:py-24"> - <h1 className="head-alt-base lg:head-alt-lg mb-8"> - 404ka: tak tahle stránka tu nenĂ - </h1> - <p className="text-base lg:text-xl mb-8"> - Dostal/a ses na takzvanou „<strong>ÄŤtyĹ™ystaÄŤtyĹ™ku</strong>“, coĹľ znamená, - Ĺľe stránka, kterou jsi se pokusil/a navštĂvit, na tomhle webu nenĂ. - Zkontroluj, zda máš správnĂ˝ odkaz. - </p> - <Button routerTo="/" className="text-base lg:text-xl" hoverActive fullwidth> - PĹ™ejĂt na hlavnĂ stránku - </Button> - </article> -); - const BaseApp = () => { initializeWSChannel.read(); return ( <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" /> + <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 />} /> diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index ce7d9a0ec8d8c947d6326e9e032ad13af86619d5..34a8dc67b5e4d68c074ffdb6fac0563a8c13b5af 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -126,6 +126,7 @@ const Navbar = () => { <button onClick={logout} className="text-grey-200 hover:text-white" + title="Odhlásit se" > <i className="ico--log-out"></i> </button> diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index 7b470dcf39748eb4488f5cad8f5fd32c0574d6ee..1115287ce3a4222edeca1c5b09fd91730f4fd43a 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from "react"; +import { Helmet } from "react-helmet"; import Joyride, { EVENTS } from "react-joyride"; import ReactPlayer from "react-player/lazy"; import useWindowSize from "@rooks/use-window-size"; @@ -109,6 +110,21 @@ const Home = () => { return ( <> + <Helmet> + <title>PĹ™ĂmĂ˝ pĹ™enos | CF 2021 | Pirátská strana</title> + <meta + name="description" + content="PĹ™ĂmĂ˝ pĹ™enos a diskuse z on-line zasedánĂ CelostátnĂho fĂłra ÄŚeskĂ© pirátskĂ© strany, 9. 1. 2021." + /> + <meta + property="og:title" + content="PĹ™ĂmĂ˝ pĹ™enos | CF 2021 | Pirátská strana" + /> + <meta + property="og:description" + content="PĹ™ĂmĂ˝ pĹ™enos a diskuse z on-line zasedánĂ CelostátnĂho fĂłra ÄŚeskĂ© pirátskĂ© strany, 9. 1. 2021." + /> + </Helmet> <Joyride beaconComponent={Beacon} continuous={true} @@ -259,8 +275,7 @@ const Home = () => { showAddPostCta={programEntry.discussionOpened} /> {!programEntry.discussionOpened && - isAuthenticated && - !user.isBanned && ( + (!isAuthenticated || (isAuthenticated && !user.isBanned)) && ( <p className="leading-normal"> Rozprava je uzavĹ™ena - pĹ™ĂspÄ›vky teÄŹ nelze pĹ™idávat. </p> diff --git a/src/pages/NotFound.jsx b/src/pages/NotFound.jsx new file mode 100644 index 0000000000000000000000000000000000000000..ee171f42370e2fec7edddbbad7619e9e8bc2633d --- /dev/null +++ b/src/pages/NotFound.jsx @@ -0,0 +1,35 @@ +import React from "react"; +import { Helmet } from "react-helmet"; + +import Button from "components/Button"; + +const NotFound = () => ( + <> + <Helmet> + <title>404ka | CF 2021 | Pirátská strana</title> + <meta name="description" content="Tahle stránka tu nenĂ." /> + <meta property="og:title" content="404ka | CF 2021 | Pirátská strana" /> + <meta property="og:description" content="Tahle stránka tu nenĂ." /> + </Helmet> + <article className="container container--default py-8 lg:py-24"> + <h1 className="head-alt-base lg:head-alt-lg mb-8"> + 404ka: tak tahle stránka tu nenĂ + </h1> + <p className="text-base lg:text-xl mb-8"> + Dostal/a ses na takzvanou „<strong>ÄŤtyĹ™ystaÄŤtyĹ™ku</strong>“, coĹľ + znamená, Ĺľe stránka, kterou jsi se pokusil/a navštĂvit, na tomhle webu + nenĂ. Zkontroluj, zda máš správnĂ˝ odkaz. + </p> + <Button + routerTo="/" + className="text-base lg:text-xl" + hoverActive + fullwidth + > + PĹ™ejĂt na hlavnĂ stránku + </Button> + </article> + </> +); + +export default NotFound; diff --git a/src/pages/Program.jsx b/src/pages/Program.jsx index 7b5c1088499555379829916a0bdeb30303dc5b32..9e78c06e23297a82f877e320a65a6737bf1f09f1 100644 --- a/src/pages/Program.jsx +++ b/src/pages/Program.jsx @@ -1,4 +1,5 @@ import React from "react"; +import { Helmet } from "react-helmet"; import { Link } from "react-router-dom"; import { format } from "date-fns"; @@ -25,79 +26,96 @@ const Schedule = () => { ); return ( - <article className="container container--default py-8 lg:py-24"> - <h1 className="head-alt-md lg:head-alt-lg mb-8">Program zasedánĂ</h1> - <div className="flex flex-col"> - {scheduleIds.map((id) => { - const isCurrent = id === currentId; - const entry = items[id]; - const fullTitle = `${entry.number}. ${entry.title}`; - return ( - <div - className="flex flex-col md:flex-row my-4 duration-300 text-black" - key={entry.id} - > - <div className="w-28 md:text-right"> - {isCurrent && ( - <Chip condensed className="mt-2 mr-2" color="red-600"> - PrávÄ› probĂhá - </Chip> - )} - </div> - <div className="w-full md:w-32 flex flex-row md:flex-col items-center md:items-stretch md:text-right md:pr-8"> - <p className="head-heavy-xs md:head-heavy-base"> - {format(entry.expectedStartAt, "H:mm")} - </p> - <p className="ml-auto md:ml-0 head-heavy-xs md:head-heavy-xs md:text-grey-200 whitespace-no-wrap"> - {format(entry.expectedStartAt, "d. M. Y")} - </p> - </div> - <div className="flex-grow w-full"> - <h2 className="head-heavy-xs md:head-heavy-base mb-1"> - {isCurrent && <Link to="/">{fullTitle}</Link>} - {!isCurrent && fullTitle} - </h2> - <div className="flex space-x-2"> - <strong>Navrhovatel:</strong> - <span>{entry.proposer}</span> + <> + <Helmet> + <title>Program zasedánĂ | CF 2021 | Pirátská strana</title> + <meta + name="description" + content="PĹ™eÄŤtÄ›te si program on-line zasedánĂ CelostátnĂho fĂłra ÄŚeskĂ© pirátskĂ© strany, 9. 1. 2021." + /> + <meta + property="og:title" + content="Program zasedánĂ | CF 2021 | Pirátská strana" + /> + <meta + property="og:description" + content="PĹ™eÄŤtÄ›te si program on-line zasedánĂ CelostátnĂho fĂłra ÄŚeskĂ© pirátskĂ© strany, 9. 1. 2021." + /> + </Helmet> + <article className="container container--default py-8 lg:py-24"> + <h1 className="head-alt-md lg:head-alt-lg mb-8">Program zasedánĂ</h1> + <div className="flex flex-col"> + {scheduleIds.map((id) => { + const isCurrent = id === currentId; + const entry = items[id]; + const fullTitle = `${entry.number}. ${entry.title}`; + return ( + <div + className="flex flex-col md:flex-row my-4 duration-300 text-black" + key={entry.id} + > + <div className="w-28 md:text-right"> + {isCurrent && ( + <Chip condensed className="mt-2 mr-2" color="red-600"> + PrávÄ› probĂhá + </Chip> + )} </div> - {entry.description && ( - <p className="mt-2 leading-tight max-w-3xl"> - {entry.description} + <div className="w-full md:w-32 flex flex-row md:flex-col items-center md:items-stretch md:text-right md:pr-8"> + <p className="head-heavy-xs md:head-heavy-base"> + {format(entry.expectedStartAt, "H:mm")} </p> - )} - {isAuthenticated && - user.role === "chairman" && - entry.id !== currentId && ( - <div className="mt-4"> - <Button - onClick={() => setEntryToActivate(entry)} - color="grey-125" - className="text-xs" - > - Aktivovat tento bod programu - </Button> - </div> + <p className="ml-auto md:ml-0 head-heavy-xs md:head-heavy-xs md:text-grey-200 whitespace-no-wrap"> + {format(entry.expectedStartAt, "d. M. Y")} + </p> + </div> + <div className="flex-grow w-full"> + <h2 className="head-heavy-xs md:head-heavy-base mb-1"> + {isCurrent && <Link to="/">{fullTitle}</Link>} + {!isCurrent && fullTitle} + </h2> + <div className="flex space-x-2"> + <strong>Navrhovatel:</strong> + <span>{entry.proposer}</span> + </div> + {entry.description && ( + <p className="mt-2 leading-tight max-w-3xl"> + {entry.description} + </p> )} + {isAuthenticated && + user.role === "chairman" && + entry.id !== currentId && ( + <div className="mt-4"> + <Button + onClick={() => setEntryToActivate(entry)} + color="grey-125" + className="text-xs" + > + Aktivovat tento bod programu + </Button> + </div> + )} + </div> </div> - </div> - ); - })} - </div> - <ModalConfirm - isOpen={!!entryToActivate} - onConfirm={onActivateConfirm} - onCancel={onActivateCancel} - confirming={activating} - error={activationError} - title="Aktivovat bod programu?" - yesActionLabel="Aktivovat" - > - PogramovanĂ˝ bod{" "} - <strong>{entryToActivate && entryToActivate.title}</strong> bude - aktivován. Chcete pokraÄŤovat? - </ModalConfirm> - </article> + ); + })} + </div> + <ModalConfirm + isOpen={!!entryToActivate} + onConfirm={onActivateConfirm} + onCancel={onActivateCancel} + confirming={activating} + error={activationError} + title="Aktivovat bod programu?" + yesActionLabel="Aktivovat" + > + PogramovanĂ˝ bod{" "} + <strong>{entryToActivate && entryToActivate.title}</strong> bude + aktivován. Chcete pokraÄŤovat? + </ModalConfirm> + </article> + </> ); }; diff --git a/src/pages/Protocol.jsx b/src/pages/Protocol.jsx index 35709933cd8945441ca8448d8539d857b9c9f8d9..9b51c4ea2cbc3c18b54696f7f0047b84ad590382 100644 --- a/src/pages/Protocol.jsx +++ b/src/pages/Protocol.jsx @@ -1,4 +1,5 @@ import React, { useCallback, useState } from "react"; +import { Helmet } from "react-helmet"; import useInterval from "@rooks/use-interval"; import { loadProtocol } from "actions/global-info"; @@ -54,56 +55,73 @@ const Protocol = () => { }; return ( - <article className="container container--default py-8 lg:py-24"> - <h1 className="head-alt-md lg:head-alt-lg mb-8">Zápis z jednánĂ</h1> + <> + <Helmet> + <title>Zápis ze zasedánĂ | CF 2021 | Pirátská strana</title> + <meta + name="description" + content="InteraktivnĂ zápis z on-line zasedánĂ CelostátnĂho fĂłra ÄŚeskĂ© pirátskĂ© strany, 9. 1. 2021." + /> + <meta + property="og:title" + content="Zápis ze zasedánĂ | CF 2021 | Pirátská strana" + /> + <meta + property="og:description" + content="InteraktivnĂ zápis z on-line zasedánĂ CelostátnĂho fĂłra ÄŚeskĂ© pirátskĂ© strany, 9. 1. 2021." + /> + </Helmet> + <article className="container container--default py-8 lg:py-24"> + <h1 className="head-alt-md lg:head-alt-lg mb-8">Zápis z jednánĂ</h1> - <div className="flex items-start"> - <div className="lg:w-2/3"> - {!protocolUrl && ( - <ErrorMessage>Zápis momentálnÄ› nenĂ k dispozici.</ErrorMessage> - )} - {protocolLoadError && ( - <ErrorMessage> - PĹ™i stahovánĂ záznamu z jednánĂ došlo k problĂ©mu:{" "} - {protocolLoadError.toString()} - </ErrorMessage> - )} - {protocolUrl && <></>} - {htmlContent && ( - <div - className="leading-tight text-sm lg:text-base content-block" - dangerouslySetInnerHTML={htmlContent} - ></div> - )} - </div> - <div className="hidden lg:block card elevation-10 w-1/3"> - <div className="lg:card__body content-block"> - <h2>Jak to funguje?</h2> - <p> - Zápis se aktualizuje automaticky kaĹľdĂ˝ch 10 sekund. Pokud chceš - aktualizaci vynutit ruÄŤnÄ›, mĹŻĹľeš vyuĹľĂt tlaÄŤĂtko nĂĹľe. - </p> - <Button - icon="ico--refresh" - loading={protocolLoading} - className="btn--fullwidth" - onClick={forceLoad} - color="black" - bodyProps={{ - style: { - position: "relative", - }, - }} - > - <span style={progressStyle}></span> - <span style={{ position: "relative" }}> - {protocolLoading ? "Aktualizace..." : "Aktualizovat"} - </span> - </Button> + <div className="flex items-start"> + <div className="lg:w-2/3"> + {!protocolUrl && ( + <ErrorMessage>Zápis momentálnÄ› nenĂ k dispozici.</ErrorMessage> + )} + {protocolLoadError && ( + <ErrorMessage> + PĹ™i stahovánĂ záznamu z jednánĂ došlo k problĂ©mu:{" "} + {protocolLoadError.toString()} + </ErrorMessage> + )} + {protocolUrl && <></>} + {htmlContent && ( + <div + className="leading-tight text-sm lg:text-base content-block" + dangerouslySetInnerHTML={htmlContent} + ></div> + )} + </div> + <div className="hidden lg:block card elevation-10 w-1/3"> + <div className="lg:card__body content-block"> + <h2>Jak to funguje?</h2> + <p> + Zápis se aktualizuje automaticky kaĹľdĂ˝ch 10 sekund. Pokud chceš + aktualizaci vynutit ruÄŤnÄ›, mĹŻĹľeš vyuĹľĂt tlaÄŤĂtko nĂĹľe. + </p> + <Button + icon="ico--refresh" + loading={protocolLoading} + className="btn--fullwidth" + onClick={forceLoad} + color="black" + bodyProps={{ + style: { + position: "relative", + }, + }} + > + <span style={progressStyle}></span> + <span style={{ position: "relative" }}> + {protocolLoading ? "Aktualizace..." : "Aktualizovat"} + </span> + </Button> + </div> </div> </div> - </div> - </article> + </article> + </> ); };