Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • cf2023-euro
  • cf2023-offline
  • cf2024
  • cf2025
  • main
5 results

Target

Select target project
  • to/cf-online-ui
  • vpfafrin/cf2021
2 results
Select Git revision
  • master
1 result
Show changes
Showing
with 225 additions and 174 deletions
...@@ -4,7 +4,7 @@ import pick from "lodash/pick"; ...@@ -4,7 +4,7 @@ import pick from "lodash/pick";
import property from "lodash/property"; import property from "lodash/property";
import { createAsyncAction, errorResult, successResult } from "pullstate"; import { createAsyncAction, errorResult, successResult } from "pullstate";
import { fetch } from "api"; import { fetchApi } from "api";
import { markdownConverter } from "markdown"; import { markdownConverter } from "markdown";
import { ProgramStore } from "stores"; import { ProgramStore } from "stores";
...@@ -13,7 +13,7 @@ import { loadPosts } from "./posts"; ...@@ -13,7 +13,7 @@ import { loadPosts } from "./posts";
export const loadProgram = createAsyncAction( export const loadProgram = createAsyncAction(
async () => { async () => {
try { try {
const resp = await fetch("/program"); const resp = await fetchApi("/program");
const mappings = await resp.json(); const mappings = await resp.json();
return successResult(mappings); return successResult(mappings);
} catch (err) { } catch (err) {
...@@ -49,17 +49,17 @@ export const loadProgram = createAsyncAction( ...@@ -49,17 +49,17 @@ export const loadProgram = createAsyncAction(
expectedStartAt: parse( expectedStartAt: parse(
entry.expected_start_at, entry.expected_start_at,
"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss",
new Date() new Date(),
), ),
expectedFinishAt: entry.expected_finish_at expectedFinishAt: entry.expected_finish_at
? parse( ? parse(
entry.expected_finish_at, entry.expected_finish_at,
"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss",
new Date() new Date(),
) )
: undefined, : undefined,
}; };
} },
) )
.sort((a, b) => a.expectedStartAt - b.expectedStartAt); .sort((a, b) => a.expectedStartAt - b.expectedStartAt);
...@@ -75,7 +75,7 @@ export const loadProgram = createAsyncAction( ...@@ -75,7 +75,7 @@ export const loadProgram = createAsyncAction(
}); });
} }
}, },
} },
); );
/** /**
...@@ -87,7 +87,7 @@ export const renameProgramPoint = createAsyncAction( ...@@ -87,7 +87,7 @@ export const renameProgramPoint = createAsyncAction(
const body = JSON.stringify({ const body = JSON.stringify({
title: newTitle, title: newTitle,
}); });
await fetch(`/program/${programEntry.id}`, { await fetchApi(`/program/${programEntry.id}`, {
method: "PUT", method: "PUT",
body, body,
expectedStatus: 204, expectedStatus: 204,
...@@ -108,7 +108,7 @@ export const renameProgramPoint = createAsyncAction( ...@@ -108,7 +108,7 @@ export const renameProgramPoint = createAsyncAction(
}); });
} }
}, },
} },
); );
/** /**
...@@ -124,7 +124,7 @@ export const endProgramPoint = createAsyncAction( ...@@ -124,7 +124,7 @@ export const endProgramPoint = createAsyncAction(
const body = JSON.stringify({ const body = JSON.stringify({
is_live: false, is_live: false,
}); });
await fetch(`/program/${programEntry.id}`, { await fetchApi(`/program/${programEntry.id}`, {
method: "PUT", method: "PUT",
body, body,
expectedStatus: 204, expectedStatus: 204,
...@@ -142,7 +142,7 @@ export const endProgramPoint = createAsyncAction( ...@@ -142,7 +142,7 @@ export const endProgramPoint = createAsyncAction(
}); });
} }
}, },
} },
); );
/** /**
...@@ -158,7 +158,7 @@ export const activateProgramPoint = createAsyncAction( ...@@ -158,7 +158,7 @@ export const activateProgramPoint = createAsyncAction(
const body = JSON.stringify({ const body = JSON.stringify({
is_live: true, is_live: true,
}); });
await fetch(`/program/${programEntry.id}`, { await fetchApi(`/program/${programEntry.id}`, {
method: "PUT", method: "PUT",
body, body,
expectedStatus: 204, expectedStatus: 204,
...@@ -179,7 +179,7 @@ export const activateProgramPoint = createAsyncAction( ...@@ -179,7 +179,7 @@ export const activateProgramPoint = createAsyncAction(
loadPosts.run({}, { respectCache: false }); loadPosts.run({}, { respectCache: false });
} }
}, },
} },
); );
/** /**
...@@ -195,7 +195,7 @@ export const openDiscussion = createAsyncAction( ...@@ -195,7 +195,7 @@ export const openDiscussion = createAsyncAction(
const body = JSON.stringify({ const body = JSON.stringify({
discussion_opened: true, discussion_opened: true,
}); });
await fetch(`/program/${programEntry.id}`, { await fetchApi(`/program/${programEntry.id}`, {
method: "PUT", method: "PUT",
body, body,
expectedStatus: 204, expectedStatus: 204,
...@@ -215,7 +215,7 @@ export const openDiscussion = createAsyncAction( ...@@ -215,7 +215,7 @@ export const openDiscussion = createAsyncAction(
}); });
} }
}, },
} },
); );
/** /**
...@@ -227,7 +227,7 @@ export const closeDiscussion = createAsyncAction( ...@@ -227,7 +227,7 @@ export const closeDiscussion = createAsyncAction(
const body = JSON.stringify({ const body = JSON.stringify({
discussion_opened: false, discussion_opened: false,
}); });
await fetch(`/program/${programEntry.id}`, { await fetchApi(`/program/${programEntry.id}`, {
method: "PUT", method: "PUT",
body, body,
expectedStatus: 204, expectedStatus: 204,
...@@ -247,5 +247,5 @@ export const closeDiscussion = createAsyncAction( ...@@ -247,5 +247,5 @@ export const closeDiscussion = createAsyncAction(
}); });
} }
}, },
} },
); );
import * as Sentry from "@sentry/react"; import * as Sentry from "@sentry/react";
import { createAsyncAction, errorResult, successResult } from "pullstate"; import { createAsyncAction, errorResult, successResult } from "pullstate";
import { fetch } from "api"; import { fetchApi } from "api";
import keycloak from "keycloak"; import keycloak from "keycloak";
import { AuthStore, PostStore } from "stores"; import { AuthStore, PostStore } from "stores";
import { updateWindowPosts } from "utils"; import { updateWindowPosts } from "utils";
...@@ -12,7 +12,7 @@ export const loadMe = createAsyncAction( ...@@ -12,7 +12,7 @@ export const loadMe = createAsyncAction(
*/ */
async () => { async () => {
try { try {
const response = await fetch(`/users/me`, { const response = await fetchApi(`/users/me`, {
method: "GET", method: "GET",
expectedStatus: 200, expectedStatus: 200,
}); });
...@@ -35,7 +35,7 @@ export const loadMe = createAsyncAction( ...@@ -35,7 +35,7 @@ export const loadMe = createAsyncAction(
}); });
} }
}, },
} },
); );
export const ban = createAsyncAction( export const ban = createAsyncAction(
...@@ -44,7 +44,7 @@ export const ban = createAsyncAction( ...@@ -44,7 +44,7 @@ export const ban = createAsyncAction(
*/ */
async (user) => { async (user) => {
try { try {
await fetch(`/users/${user.id}/ban`, { await fetchApi(`/users/${user.id}/ban`, {
method: "PATCH", method: "PATCH",
expectedStatus: 204, expectedStatus: 204,
}); });
...@@ -52,7 +52,7 @@ export const ban = createAsyncAction( ...@@ -52,7 +52,7 @@ export const ban = createAsyncAction(
} catch (err) { } catch (err) {
return errorResult([], err.toString()); return errorResult([], err.toString());
} }
} },
); );
export const unban = createAsyncAction( export const unban = createAsyncAction(
...@@ -61,7 +61,7 @@ export const unban = createAsyncAction( ...@@ -61,7 +61,7 @@ export const unban = createAsyncAction(
*/ */
async (user) => { async (user) => {
try { try {
await fetch(`/users/${user.id}/unban`, { await fetchApi(`/users/${user.id}/unban`, {
method: "PATCH", method: "PATCH",
expectedStatus: 204, expectedStatus: 204,
}); });
...@@ -69,7 +69,7 @@ export const unban = createAsyncAction( ...@@ -69,7 +69,7 @@ export const unban = createAsyncAction(
} catch (err) { } catch (err) {
return errorResult([], err.toString()); return errorResult([], err.toString());
} }
} },
); );
export const inviteToJitsi = createAsyncAction( export const inviteToJitsi = createAsyncAction(
...@@ -81,7 +81,7 @@ export const inviteToJitsi = createAsyncAction( ...@@ -81,7 +81,7 @@ export const inviteToJitsi = createAsyncAction(
const body = JSON.stringify({ const body = JSON.stringify({
allowed: true, allowed: true,
}); });
await fetch(`/users/${user.id}/jitsi`, { await fetchApi(`/users/${user.id}/jitsi`, {
method: "PATCH", method: "PATCH",
body, body,
expectedStatus: 204, expectedStatus: 204,
...@@ -90,7 +90,7 @@ export const inviteToJitsi = createAsyncAction( ...@@ -90,7 +90,7 @@ export const inviteToJitsi = createAsyncAction(
} catch (err) { } catch (err) {
return errorResult([], err.toString()); return errorResult([], err.toString());
} }
} },
); );
export const refreshAccessToken = async () => { export const refreshAccessToken = async () => {
...@@ -105,7 +105,7 @@ export const refreshAccessToken = async () => { ...@@ -105,7 +105,7 @@ export const refreshAccessToken = async () => {
console.info("[auth] access token refreshed"); console.info("[auth] access token refreshed");
} catch (exc) { } catch (exc) {
console.warn( console.warn(
"[auth] could not refresh the access token, refresh token possibly expired, logging out" "[auth] could not refresh the access token, refresh token possibly expired, logging out",
); );
Sentry.setUser(null); Sentry.setUser(null);
......
import baseFetch from "unfetch";
import { AuthStore } from "./stores"; import { AuthStore } from "./stores";
export const fetch = async ( export const fetchApi = async (
url, url,
{ headers = {}, expectedStatus = 200, method = "GET", body = null } = {} { headers = {}, expectedStatus = 200, method = "GET", body = null } = {},
) => { ) => {
const { isAuthenticated, user } = AuthStore.getRawState(); const { isAuthenticated, user } = AuthStore.getRawState();
...@@ -16,10 +14,11 @@ export const fetch = async ( ...@@ -16,10 +14,11 @@ export const fetch = async (
headers["Content-Type"] = "application/json"; headers["Content-Type"] = "application/json";
} }
const response = await baseFetch(process.env.REACT_APP_API_BASE_URL + url, { const response = await fetch(process.env.REACT_APP_API_BASE_URL + url, {
body, body,
method, method,
headers, headers,
redirect: "follow",
}); });
if (!!expectedStatus && response.status !== expectedStatus) { if (!!expectedStatus && response.status !== expectedStatus) {
......
import React from "react"; import React, { useState } from "react";
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import useWindowSize from "@rooks/use-window-size";
import classNames from "classnames";
const Footer = () => { const Footer = () => {
const { innerWidth } = useWindowSize();
const [showCfMenu, setShowCfMenu] = useState(false);
const [showOtherMenu, setShowOtherMenu] = useState(false);
const isLg = innerWidth >= 1024;
return ( return (
<footer className="footer bg-grey-700 text-white"> <footer className="footer bg-grey-700 text-white">
<div className="footer__main py-4 lg:py-16 container container--default"> <div className="footer__main py-4 lg:py-16 container container--default">
...@@ -12,17 +19,25 @@ const Footer = () => { ...@@ -12,17 +19,25 @@ const Footer = () => {
className="w-32 md:w-40 pb-6" className="w-32 md:w-40 pb-6"
/> />
<p className="para hidden md:block md:mb-4 lg:mb-0 text-grey-200"> <p className="para hidden md:block md:mb-4 lg:mb-0 text-grey-200">
Piráti, 2021. Všechna práva vyhlazena. Sdílejte a nechte ostatní Piráti, 2024. Všechna práva vyhlazena. Sdílejte a nechte ostatní
sdílet za stejných podmínek. sdílet za stejných podmínek.
</p> </p>
</section> </section>
<section className="footer__main-links bg-grey-700 text-white lg:grid grid-cols-2 gap-4"> <section className="footer__main-links bg-grey-700 text-white lg:grid grid-cols-2 gap-4">
<div className="pt-8 pb-4 lg:py-0"> <div className="pt-8 pb-4 lg:py-0">
<div className="footer-collapsible"> <div className="footer-collapsible">
<span className="text-xl uppercase text-white footer-collapsible__toggle"> <span
CF 2021 className={classNames(
"text-xl uppercase text-white footer-collapsible__toggle",
{
"footer-collapsible__toggle--open": showCfMenu,
}
)}
onClick={() => setShowCfMenu(!showCfMenu)}
>
CF 2024
</span>{" "} </span>{" "}
<div className=""> <div className={showCfMenu || isLg ? "" : "hidden"}>
<ul className="mt-6 space-y-2 text-grey-200"> <ul className="mt-6 space-y-2 text-grey-200">
<li> <li>
<NavLink to="/">Přímý přenos</NavLink> <NavLink to="/">Přímý přenos</NavLink>
...@@ -42,10 +57,18 @@ const Footer = () => { ...@@ -42,10 +57,18 @@ const Footer = () => {
</div> </div>
<div className="py-4 lg:py-0 border-t border-grey-400 lg:border-t-0"> <div className="py-4 lg:py-0 border-t border-grey-400 lg:border-t-0">
<div className="footer-collapsible"> <div className="footer-collapsible">
<span className="text-xl uppercase text-white footer-collapsible__toggle"> <span
className={classNames(
"text-xl uppercase text-white footer-collapsible__toggle",
{
"footer-collapsible__toggle--open": showOtherMenu,
}
)}
onClick={() => setShowOtherMenu(!showOtherMenu)}
>
Otevřenost Otevřenost
</span>{" "} </span>{" "}
<div className=""> <div className={showOtherMenu || isLg ? "" : "hidden"}>
<ul className="mt-6 space-y-2 text-grey-200"> <ul className="mt-6 space-y-2 text-grey-200">
<li> <li>
<a href="https://ucet.pirati.cz">Transparentní účet</a> <a href="https://ucet.pirati.cz">Transparentní účet</a>
......
...@@ -77,7 +77,7 @@ const Navbar = ({ onGetHelp }) => { ...@@ -77,7 +77,7 @@ const Navbar = ({ onGetHelp }) => {
to="/" to="/"
className="pl-4 font-bold text-xl lg:border-r lg:border-grey-300 lg:pr-8 hover:no-underline" 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 Celostátní fórum 2024
</NavLink> </NavLink>
</div> </div>
<div className="navbar__menutoggle my-4 flex justify-end lg:hidden"> <div className="navbar__menutoggle my-4 flex justify-end lg:hidden">
......
...@@ -9,7 +9,7 @@ const NotYetStarted = ({ startAt }) => ( ...@@ -9,7 +9,7 @@ const NotYetStarted = ({ startAt }) => (
Jejda ... Jejda ...
</div> </div>
<h1 className="head-alt-base md:head-alt-md lg:head-alt-xl mb-2"> <h1 className="head-alt-base md:head-alt-md lg:head-alt-xl mb-2">
Jednání ještě nebylo zahájeno :( Jednání ještě nebylo zahájeno
</h1> </h1>
<p className="text-xl leading-snug mb-8"> <p className="text-xl leading-snug mb-8">
<span>Jednání celostátního fóra ještě nezačalo. </span> <span>Jednání celostátního fóra ještě nezačalo. </span>
......
import React from "react"; import React from "react";
import Chip from "components/Chip"; // import Chip from "components/Chip";
export { default as Beacon } from "./Beacon"; export { default as Beacon } from "./Beacon";
...@@ -9,11 +9,12 @@ export const steps = [ ...@@ -9,11 +9,12 @@ export const steps = [
target: "body", target: "body",
content: ( content: (
<> <>
<h1 className="head-alt-sm mb-4">Vítej na celostátním fóru 2021</h1> <h1 className="head-alt-sm mb-4">Vítej na celostátním fóru 2024</h1>
<p className="leading-snug text-base"> <p className="leading-snug text-base">
Letošní Pirátské fórum bude online. Abychom to celé zvládli, Víme, že volebního zasedání se nemohou zúčastnit všichni.
připravili jsme tuhle aplikaci, která se snaží alespoň částečně Abychom nepřítomným umožnili zasedání lépe sledovat, připravili
nahradit fyzickou přítomnost. Nejprve si vysvětlíme, jak funguje. jsme tuhle aplikaci, která umožňuje zasáhnout do rozpravy.
Nejprve si vysvětlíme, jak funguje.
</p> </p>
</> </>
), ),
...@@ -60,24 +61,6 @@ export const steps = [ ...@@ -60,24 +61,6 @@ export const steps = [
<p> <p>
<strong>Běžné příspěvky</strong> se zobrazí ihned po přidání. <strong>Běžné příspěvky</strong> se zobrazí ihned po přidání.
</p> </p>
<p>
<strong>Návrhy postupu</strong> po přidání nejprve zkontroluje
předsedající a pokud sezná, že je takový návrh přípusný, prohlásí ho
za{" "}
<Chip color="blue-300" condensed>
hlasovatelný
</Chip>
. Pro vyjádření podpory používej palce. Na základě míry podpory
předsedající buď návrh označí za{" "}
<Chip color="green-400" condensed>
schválený
</Chip>
, nebo za{" "}
<Chip color="red-600" condensed>
zamítnutý
</Chip>
.
</p>
<p> <p>
U příspěvků se též zobrazuje celková míra podpory. Legenda barevného U příspěvků se též zobrazuje celková míra podpory. Legenda barevného
odlišení je následující: odlišení je následující:
...@@ -110,6 +93,11 @@ export const steps = [ ...@@ -110,6 +93,11 @@ export const steps = [
je označen příspěvek, který zatím není ohodnocen. je označen příspěvek, který zatím není ohodnocen.
</li> </li>
</ul> </ul>
<p>
<strong>Návrhy postupui</strong> po přidání nejprve zkontroluje předsedající a pokud sezná,
že je takový návrh přípusný, prohlásí ho za hlasovatelný a předloží k hlasování
v plénu. Na základě toho návrh předsedající označí za schválený, nebo za zamítnutý.
</p>
</div> </div>
</> </>
), ),
...@@ -152,7 +140,7 @@ export const steps = [ ...@@ -152,7 +140,7 @@ export const steps = [
<> <>
<h1 className="head-alt-sm mb-4">To je vše!</h1> <h1 className="head-alt-sm mb-4">To je vše!</h1>
<p className="leading-snug text-base"> <p className="leading-snug text-base">
Ať se ti letošní „CFko“ líbí i v těchto ztížených podmínkách. Ať se ti letošní „CFko“ líbí.
</p> </p>
</> </>
), ),
......
...@@ -201,7 +201,7 @@ const Post = ({ ...@@ -201,7 +201,7 @@ const Post = ({
className="w-8 h-8 lg:w-14 lg:h-14 mr-3 rounded object-cover" className="w-8 h-8 lg:w-14 lg:h-14 mr-3 rounded object-cover"
alt={author.name} alt={author.name}
/> />
<div className="flex-1 w-full"> <div className="flex-1 overflow-hidden">
<div className="mb-1"> <div className="mb-1">
<div className="flex justify-between items-start xl:items-center"> <div className="flex justify-between items-start xl:items-center">
<div className="flex flex-col xl:flex-row xl:items-center"> <div className="flex flex-col xl:flex-row xl:items-center">
...@@ -248,7 +248,7 @@ const Post = ({ ...@@ -248,7 +248,7 @@ const Post = ({
supportThreshold={supportThreshold} supportThreshold={supportThreshold}
/> />
{showActions && ( {showActions && (
<DropdownMenu right className="pl-4"> <DropdownMenu right className="pl-4 static">
{showAnnounceAction && ( {showAnnounceAction && (
<DropdownMenuItem <DropdownMenuItem
onClick={onAnnounceProcedureProposal} onClick={onAnnounceProcedureProposal}
...@@ -327,12 +327,10 @@ const Post = ({ ...@@ -327,12 +327,10 @@ const Post = ({
<div className="flex lg:hidden flex-row flex-wrap my-2 space-x-2"> <div className="flex lg:hidden flex-row flex-wrap my-2 space-x-2">
{labels} {labels}
</div> </div>
<div className="overflow-hidden"> <div
<div className="text-sm lg:text-base text-black leading-normal content-block overflow-x-auto overflow-y-hidden mt-1"
className="text-sm lg:text-base text-black leading-normal content-block overflow-x-scroll overflow-y-hidden" dangerouslySetInnerHTML={htmlContent}
dangerouslySetInnerHTML={htmlContent} ></div>
></div>
</div>
</div> </div>
</div> </div>
); );
......
...@@ -46,6 +46,8 @@ const AddAnnouncementForm = ({ className }) => { ...@@ -46,6 +46,8 @@ const AddAnnouncementForm = ({ className }) => {
}; };
const onAdd = async (evt) => { const onAdd = async (evt) => {
evt.preventDefault();
let preventAction = false; let preventAction = false;
const payload = { const payload = {
content: text, content: text,
...@@ -82,7 +84,7 @@ const AddAnnouncementForm = ({ className }) => { ...@@ -82,7 +84,7 @@ const AddAnnouncementForm = ({ className }) => {
}; };
return ( return (
<div className={className}> <form className={className} onSubmit={onAdd}>
{addingError && ( {addingError && (
<ErrorMessage> <ErrorMessage>
Při přidávání oznámení došlo k problému: {addingError}. Při přidávání oznámení došlo k problému: {addingError}.
...@@ -150,7 +152,7 @@ const AddAnnouncementForm = ({ className }) => { ...@@ -150,7 +152,7 @@ const AddAnnouncementForm = ({ className }) => {
</div> </div>
<Button <Button
onClick={onAdd} type="submit"
className="text-sm mt-4" className="text-sm mt-4"
hoverActive hoverActive
loading={adding} loading={adding}
...@@ -159,7 +161,7 @@ const AddAnnouncementForm = ({ className }) => { ...@@ -159,7 +161,7 @@ const AddAnnouncementForm = ({ className }) => {
> >
Přidat oznámení Přidat oznámení
</Button> </Button>
</div> </form>
); );
}; };
......
...@@ -10,7 +10,7 @@ import ErrorMessage from "components/ErrorMessage"; ...@@ -10,7 +10,7 @@ import ErrorMessage from "components/ErrorMessage";
import MarkdownEditor from "components/mde/MarkdownEditor"; import MarkdownEditor from "components/mde/MarkdownEditor";
import { useActionState } from "hooks"; import { useActionState } from "hooks";
const AddPostForm = ({ className }) => { const AddPostForm = ({ className, canAddProposal }) => {
const cardRef = useRef(); const cardRef = useRef();
const editorRef = useRef(); const editorRef = useRef();
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
...@@ -34,16 +34,24 @@ const AddPostForm = ({ className }) => { ...@@ -34,16 +34,24 @@ const AddPostForm = ({ className }) => {
setExpanded(false); setExpanded(false);
}, [setExpanded]); }, [setExpanded]);
const onWrite = useCallback(() => { const onWrite = useCallback(
if (!expanded) { (evt) => {
setExpanded(true); setShowAddConfirm(false);
setTimeout(() => {
if (editorRef.current && editorRef.current.finalRefs.textarea.current) { if (!expanded) {
editorRef.current.finalRefs.textarea.current.focus(); setExpanded(true);
} setTimeout(() => {
}, 0); if (
} editorRef.current &&
}, [setExpanded, expanded]); editorRef.current.finalRefs.textarea.current
) {
editorRef.current.finalRefs.textarea.current.focus();
}
}, 0);
}
},
[setExpanded, expanded, setShowAddConfirm]
);
const hideAddConfirm = useCallback(() => { const hideAddConfirm = useCallback(() => {
setShowAddConfirm(false); setShowAddConfirm(false);
...@@ -66,6 +74,8 @@ const AddPostForm = ({ className }) => { ...@@ -66,6 +74,8 @@ const AddPostForm = ({ className }) => {
}; };
const onAdd = async (evt) => { const onAdd = async (evt) => {
evt.preventDefault();
if (!!text) { if (!!text) {
if (!error) { if (!error) {
const result = await (type === "post" ? addPost : addProposal).run({ const result = await (type === "post" ? addPost : addProposal).run({
...@@ -88,7 +98,7 @@ const AddPostForm = ({ className }) => { ...@@ -88,7 +98,7 @@ const AddPostForm = ({ className }) => {
className, className,
"hover:elevation-16 transition duration-500", "hover:elevation-16 transition duration-500",
{ {
"elevation-4 cursor-text": !expanded, "elevation-4 cursor-text": !expanded && !showAddConfirm,
"lg:elevation-16 container-padding--zero lg:container-padding--auto": expanded, "lg:elevation-16 container-padding--zero lg:container-padding--auto": expanded,
} }
); );
...@@ -117,7 +127,7 @@ const AddPostForm = ({ className }) => { ...@@ -117,7 +127,7 @@ const AddPostForm = ({ className }) => {
"p-4 lg:p-8 " + (showAddConfirm || !expanded ? "hidden" : "") "p-4 lg:p-8 " + (showAddConfirm || !expanded ? "hidden" : "")
} }
> >
<div className="space-y-4"> <form className="space-y-4" onSubmit={onAdd}>
{apiError && is429ApiError && ( {apiError && is429ApiError && (
<div className="alert alert--warning"> <div className="alert alert--warning">
<i className="alert__icon ico--clock text-lg" /> <i className="alert__icon ico--clock text-lg" />
...@@ -146,39 +156,41 @@ const AddPostForm = ({ className }) => { ...@@ -146,39 +156,41 @@ const AddPostForm = ({ className }) => {
]} ]}
/> />
<div {canAddProposal && (
className="form-field" <div
onChange={(evt) => setType(evt.target.value)} className="form-field"
> onChange={(evt) => setType(evt.target.value)}
<div className="form-field__wrapper form-field__wrapper--freeform flex-col sm:flex-row"> >
<div className="radio form-field__control"> <div className="form-field__wrapper form-field__wrapper--freeform flex-col sm:flex-row">
<label> <div className="radio form-field__control">
<input <label>
type="radio" <input
name="postType" type="radio"
value="post" name="postType"
defaultChecked value="post"
/> defaultChecked
<span className="text-sm sm:text-base"> />
Přidávám <strong>běžný příspěvek</strong> <span className="text-sm sm:text-base">
</span> Přidávám <strong>běžný příspěvek</strong>
</label> </span>
</div> </label>
</div>
<div className="radio form-field__control ml-0 mt-4 sm:mt-0 sm:ml-4">
<label> <div className="radio form-field__control ml-0 mt-4 sm:mt-0 sm:ml-4">
<input <label>
type="radio" <input
name="postType" type="radio"
value="procedure-proposal" name="postType"
/> value="procedure-proposal"
<span className="text-sm sm:text-base"> />
Přidávám <strong>návrh postupu</strong> <span className="text-sm sm:text-base">
</span> Přidávám <strong>návrh postupu</strong>
</label> </span>
</label>
</div>
</div> </div>
</div> </div>
</div> )}
{type === "procedure-proposal" && ( {type === "procedure-proposal" && (
<p className="alert alert--light text-sm"> <p className="alert alert--light text-sm">
...@@ -193,7 +205,7 @@ const AddPostForm = ({ className }) => { ...@@ -193,7 +205,7 @@ const AddPostForm = ({ className }) => {
<div className="space-x-4"> <div className="space-x-4">
<Button <Button
onClick={onAdd} type="submit"
disabled={error || addingPost || addingProposal} disabled={error || addingPost || addingProposal}
loading={addingPost || addingProposal} loading={addingPost || addingProposal}
fullwidth fullwidth
...@@ -217,7 +229,7 @@ const AddPostForm = ({ className }) => { ...@@ -217,7 +229,7 @@ const AddPostForm = ({ className }) => {
. .
</span> </span>
</div> </div>
</div> </form>
</CardBody> </CardBody>
</Card> </Card>
); );
......
...@@ -8,6 +8,10 @@ import { useActionState } from "hooks"; ...@@ -8,6 +8,10 @@ import { useActionState } from "hooks";
import { AuthStore } from "stores"; import { AuthStore } from "stores";
const JitsiInviteCard = () => { const JitsiInviteCard = () => {
// docasne zablokovano
return null;
const { showJitsiInvitePopup, jitsiPopupDismissed } = AuthStore.useState(); const { showJitsiInvitePopup, jitsiPopupDismissed } = AuthStore.useState();
const [loading, errorMessage] = useActionState(loadMe); const [loading, errorMessage] = useActionState(loadMe);
......
...@@ -22,16 +22,12 @@ const PostFilters = () => { ...@@ -22,16 +22,12 @@ const PostFilters = () => {
{ title: "Jen příspěvky", value: "discussionOnly" }, { title: "Jen příspěvky", value: "discussionOnly" },
]; ];
const setFilter = (prop, newValue, resetPage = true) => { const setFilter = (prop, newValue) => {
PostStore.update((state) => { PostStore.update((state) => {
state.filters[prop] = newValue; state.filters[prop] = newValue;
state.window.itemCount = state.window.items.length; state.window.itemCount = state.window.items.length;
updateWindowPosts(state); updateWindowPosts(state);
if (resetPage) {
state.window.page = 1;
}
}); });
}; };
......
...@@ -13,7 +13,7 @@ export const useItemActionConfirm = (actionFn, actionParamsBuilder = null) => { ...@@ -13,7 +13,7 @@ export const useItemActionConfirm = (actionFn, actionParamsBuilder = null) => {
if (item) { if (item) {
const newActionArgs = (actionParamsBuilder || baseActionParamsBuilder)( const newActionArgs = (actionParamsBuilder || baseActionParamsBuilder)(
item, item,
args args,
); );
setActionArgs(newActionArgs); setActionArgs(newActionArgs);
const result = await actionFn.run(newActionArgs); const result = await actionFn.run(newActionArgs);
...@@ -23,7 +23,7 @@ export const useItemActionConfirm = (actionFn, actionParamsBuilder = null) => { ...@@ -23,7 +23,7 @@ export const useItemActionConfirm = (actionFn, actionParamsBuilder = null) => {
} }
} }
}, },
[item, setItem, actionFn, actionParamsBuilder, setActionArgs] [item, setItem, actionFn, actionParamsBuilder, setActionArgs],
); );
const onActionCancel = useCallback(() => { const onActionCancel = useCallback(() => {
......
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom/client";
import ReactModal from "react-modal"; import ReactModal from "react-modal";
import { refreshAccessToken } from "actions/users"; import { refreshAccessToken } from "actions/users";
...@@ -7,7 +7,7 @@ import { refreshAccessToken } from "actions/users"; ...@@ -7,7 +7,7 @@ import { refreshAccessToken } from "actions/users";
import App from "./App"; import App from "./App";
import * as serviceWorker from "./serviceWorker"; import * as serviceWorker from "./serviceWorker";
const root = document.getElementById("root"); const root = ReactDOM.createRoot(document.getElementById("root"));
function handleVisibilityChange() { function handleVisibilityChange() {
if (!document.hidden) { if (!document.hidden) {
...@@ -17,14 +17,12 @@ function handleVisibilityChange() { ...@@ -17,14 +17,12 @@ function handleVisibilityChange() {
document.addEventListener("visibilitychange", handleVisibilityChange, false); document.addEventListener("visibilitychange", handleVisibilityChange, false);
ReactDOM.render( root.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>
root
); );
ReactModal.setAppElement(document.getElementById("root"));
ReactModal.setAppElement(root);
// If you want your app to work offline and load faster, you can change // If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls. // unregister() to register() below. Note this comes with some pitfalls.
......
...@@ -2,7 +2,7 @@ import Keycloak from "keycloak-js"; ...@@ -2,7 +2,7 @@ import Keycloak from "keycloak-js";
// Setup Keycloak instance as needed // Setup Keycloak instance as needed
// Pass initialization options as required or leave blank to load from 'keycloak.json' // Pass initialization options as required or leave blank to load from 'keycloak.json'
const keycloak = Keycloak({ const keycloak = new Keycloak({
url: "https://auth.pirati.cz/auth", url: "https://auth.pirati.cz/auth",
realm: "pirati", realm: "pirati",
clientId: "cf-online", clientId: "cf-online",
......
...@@ -6,20 +6,30 @@ import { markdownConverter } from "markdown"; ...@@ -6,20 +6,30 @@ import { markdownConverter } from "markdown";
const content = markdownConverter.makeHtml(` const content = markdownConverter.makeHtml(`
**Celostátní fórum Pirátské strany** je [podle Stanov](https://wiki.pirati.cz/rules/st#cl_8_celostatni_forum) nejvyšším orgánem strany a zasedání se podle možností účastní každý člen strany. **Celostátní fórum Pirátské strany** je [podle Stanov](https://wiki.pirati.cz/rules/st#cl_8_celostatni_forum) nejvyšším orgánem strany a zasedání se podle možností účastní každý člen strany.
Celostátní fórum ve výlučné působnosti: > #### Celostátní fórum ve výlučné působnosti:
>
> * a. volí a odvolává republikové předsednictvo,
> * b. volí a odvolává členy republikového výboru volené celostátním fórem,
> * c. zřizuje a ruší komise a odbory na celostátní úrovni,
> * d. volí a odvolává členy komise a vedoucího odboru,
> * e. schvaluje změny stanov,
> * f. projednává a schvaluje výroční zprávu předsedy strany,
> * g. mimořádně přezkoumává rozhodnutí orgánu strany,
> * h. schvaluje zakládací dokument politického institutu,
> * i. může schválit Předpis o institutu,
> * j. může volit a odvolávat některé členy správní rady politického institutu.
>
> #### Celostátní fórum dále
>
> * a. přijímá v mezích stanov další předpisy,
> * b. ukládá úkoly republikovému předsednictvu a republikovému výboru,
> * c. může projednávat a schvalovat základní programové a ideové dokumenty,
> * d. má veškerou působnost, kterou stanovy neurčují jinému orgánu strany.
a. volí a odvolává republikové předsednictvo,
b. volí a odvolává členy republikového výboru volené celostátním fórem,
c. zřizuje a ruší komise a odbory,
d. volí a odvolává členy komise a vedoucího odboru,
e. schvaluje změny stanov,
f. projednává a schvaluje výroční zprávu předsedy strany,
g. projednává a schvaluje výroční finanční zprávu podle ZPS,
h. mimořádně přezkoumává rozhodnutí orgánu strany
### Zasedání na Internetu ### Zasedání na Internetu
Zimní zasedání Celostátního fóru, z důvodu mimořádných okolnosti spojenych s mimořádným stavem, bude probihat **na Internetu**. postup zasedání na Internetu je definovan [§42a](https://wiki.pirati.cz/rules/jdr#zasedani_na_internetu) Jednacího řádu Celostátního fóra v nasledujim znění: Zasedání Celostátního fóra může z důvodu mimořádných okolností probíhat na Internetu. Postup zasedání na Internetu je definován §42a Jednacího řádu Celostátního fóra v následujícím znění:
> **(1)** Pokud mimořádné okolnosti nedovolují konání běžného zasedání, může, v rámci krizového řízení, republikové předsednictvo pověřit předsedu strany svoláním zasedání na Internetu nebo změnou již svolaného běžného zasedání na zasedání na Internetu. > **(1)** Pokud mimořádné okolnosti nedovolují konání běžného zasedání, může, v rámci krizového řízení, republikové předsednictvo pověřit předsedu strany svoláním zasedání na Internetu nebo změnou již svolaného běžného zasedání na zasedání na Internetu.
> >
...@@ -37,10 +47,10 @@ Zimní zasedání Celostátního fóru, z důvodu mimořádných okolnosti spoje ...@@ -37,10 +47,10 @@ Zimní zasedání Celostátního fóru, z důvodu mimořádných okolnosti spoje
> >
> **(4)** Právo účasti v jednání zvukem a obrazem mají zejména: > **(4)** Právo účasti v jednání zvukem a obrazem mají zejména:
> >
> a) předsedající a další činovníci jednání, > * a) předsedající a další činovníci jednání,
> b) osoby s právem na závěrečné slovo v rozpravě k bodům k rozhodnutí, > * b) osoby s právem na závěrečné slovo v rozpravě k bodům k rozhodnutí,
> c) osoby určené navrhovatelem bodu v rozpravě k jiným bodům, > * c) osoby určené navrhovatelem bodu v rozpravě k jiným bodům,
> d) další osoby, pro něž je schválen takový postup. > * d) další osoby, pro něž je schválen takový postup.
> >
> **(5)** Jinak se při zasedání na Internetu postupuje přiměřeně jako při běžném zasedání. > **(5)** Jinak se při zasedání na Internetu postupuje přiměřeně jako při běžném zasedání.
> >
...@@ -58,14 +68,14 @@ const About = () => { ...@@ -58,14 +68,14 @@ const About = () => {
return ( return (
<> <>
<Helmet> <Helmet>
<title>Co je to celostátní fórum? | CF 2021 | Pirátská strana</title> <title>Co je to celostátní fórum? | CF 2024 | Pirátská strana</title>
<meta <meta
name="description" name="description"
content="Nevíte co je to celostátní fórum České pirátské strany? Tady se dočtete vše potřebné." content="Nevíte co je to celostátní fórum České pirátské strany? Tady se dočtete vše potřebné."
/> />
<meta <meta
property="og:title" property="og:title"
content="Co je to celostátní fórum? | CF 2021 | Pirátská strana" content="Co je to celostátní fórum? | CF 2024 | Pirátská strana"
/> />
<meta <meta
property="og:description" property="og:description"
......
...@@ -136,18 +136,18 @@ const Home = () => { ...@@ -136,18 +136,18 @@ const Home = () => {
return ( return (
<> <>
<Helmet> <Helmet>
<title>Přímý přenos | CF 2021 | Pirátská strana</title> <title>Přímý přenos | CF 2024 | Pirátská strana</title>
<meta <meta
name="description" 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." content="Přímý přenos a diskuse z on-line zasedání Celostátního fóra České pirátské strany, 13. 1. 2024."
/> />
<meta <meta
property="og:title" property="og:title"
content="Přímý přenos | CF 2021 | Pirátská strana" content="Přímý přenos | CF 2024 | Pirátská strana"
/> />
<meta <meta
property="og:description" 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." content="Přímý přenos a diskuse z on-line zasedání Celostátního fóra České pirátské strany, 13. 1. 2024."
/> />
</Helmet> </Helmet>
<Joyride <Joyride
...@@ -220,7 +220,7 @@ const Home = () => { ...@@ -220,7 +220,7 @@ const Home = () => {
onClick={showTutorial} onClick={showTutorial}
/> />
{displayActions && ( {displayActions && (
<DropdownMenu right triggerSize="lg"> <DropdownMenu right triggerSize="lg" className="z-20">
<DropdownMenuItem <DropdownMenuItem
onClick={() => setShowProgramEditModal(true)} onClick={() => setShowProgramEditModal(true)}
icon="ico--pencil" icon="ico--pencil"
...@@ -258,7 +258,11 @@ const Home = () => { ...@@ -258,7 +258,11 @@ const Home = () => {
</div> </div>
</div> </div>
</div> </div>
<section className="cf2021__video"> <section
className="cf2021__video"
// This prevents overflowing on very long lines without spaces on mobile, 2rem compensates container-padding--zero.
style={{ maxWidth: "calc(100vw - 2rem)" }}
>
<div className="container-padding--zero md:container-padding--auto"> <div className="container-padding--zero md:container-padding--auto">
{streamUrl && ( {streamUrl && (
<div className="iframe-container joyride-player"> <div className="iframe-container joyride-player">
...@@ -274,10 +278,12 @@ const Home = () => { ...@@ -274,10 +278,12 @@ const Home = () => {
</div> </div>
)} )}
{!streamUrl && ( {!streamUrl && (
<p> <div className="px-4 py-16 lg:py-48 flex items-center justify-center bg-grey-400 text-center">
Server neposlal informaci o aktuálním streamu. Vyčkejte na <span className="text-lg lg:text-xl text-grey-200">
aktualizaci. <i className="ico--warning mr-2" /> Stream teď není k
</p> dispozici. Vyčkej na aktualizaci.
</span>
</div>
)} )}
<GlobalStats /> <GlobalStats />
</div> </div>
...@@ -294,7 +300,12 @@ const Home = () => { ...@@ -294,7 +300,12 @@ const Home = () => {
</div> </div>
</section> </section>
<section className="cf2021__posts joyride-posts"> {/* Relative is for fixing the dropdowns on the right which are detached from their immediate container. */}
<section
className="cf2021__posts relative joyride-posts"
// This prevents overflowing on very long lines without spaces on mobile, 2rem compensates container-padding--zero.
style={{ maxWidth: "calc(100vw - 2rem)" }}
>
<div className="flex flex-col xl:flex-row xl:justify-between xl:items-center mb-4"> <div className="flex flex-col xl:flex-row xl:justify-between xl:items-center mb-4">
<h2 className="head-heavy-xs md:head-heavy-sm whitespace-no-wrap"> <h2 className="head-heavy-xs md:head-heavy-sm whitespace-no-wrap">
<span>Příspěvky v rozpravě</span> <span>Příspěvky v rozpravě</span>
...@@ -330,7 +341,14 @@ const Home = () => { ...@@ -330,7 +341,14 @@ const Home = () => {
)} )}
{programEntry.discussionOpened && {programEntry.discussionOpened &&
isAuthenticated && isAuthenticated &&
!user.isBanned && <AddPostForm className="mb-8" />} !user.isBanned && (
<AddPostForm
className="mb-8"
canAddProposal={
user.role === "member" || user.role === "chairman"
}
/>
)}
<PostsContainer <PostsContainer
className="container-padding--zero lg:container-padding--auto" className="container-padding--zero lg:container-padding--auto"
......
...@@ -6,9 +6,9 @@ import Button from "components/Button"; ...@@ -6,9 +6,9 @@ import Button from "components/Button";
const NotFound = () => ( const NotFound = () => (
<> <>
<Helmet> <Helmet>
<title>404ka | CF 2021 | Pirátská strana</title> <title>404ka | CF 2024 | Pirátská strana</title>
<meta name="description" content="Tahle stránka tu není." /> <meta name="description" content="Tahle stránka tu není." />
<meta property="og:title" content="404ka | CF 2021 | Pirátská strana" /> <meta property="og:title" content="404ka | CF 2024 | Pirátská strana" />
<meta property="og:description" content="Tahle stránka tu není." /> <meta property="og:description" content="Tahle stránka tu není." />
</Helmet> </Helmet>
<article className="container container--default py-8 lg:py-24"> <article className="container container--default py-8 lg:py-24">
......
...@@ -28,22 +28,25 @@ const Schedule = () => { ...@@ -28,22 +28,25 @@ const Schedule = () => {
return ( return (
<> <>
<Helmet> <Helmet>
<title>Program zasedání | CF 2021 | Pirátská strana</title> <title>Program zasedání | CF 2024 | Pirátská strana</title>
<meta <meta
name="description" 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." content="Přečtěte si program on-line zasedání Celostátního fóra České pirátské strany, 13. 1. 2024."
/> />
<meta <meta
property="og:title" property="og:title"
content="Program zasedání | CF 2021 | Pirátská strana" content="Program zasedání | CF 2024 | Pirátská strana"
/> />
<meta <meta
property="og:description" 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." content="Přečtěte si program on-line zasedání Celostátního fóra České pirátské strany, 13. 1. 2024."
/> />
</Helmet> </Helmet>
<article className="container container--default py-8 lg:py-24"> <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> <h1 className="head-alt-md lg:head-alt-lg mb-8">Program zasedání</h1>
<div class="my-4">
Program zde neobsahuje z technických důvodů všechny podrobnosti. Kompletní program naleznete na <a href="https://cf2024.pirati.cz/program">webu</a>.
</div>
<div className="flex flex-col"> <div className="flex flex-col">
{scheduleIds.map((id) => { {scheduleIds.map((id) => {
const isCurrent = id === currentId; const isCurrent = id === currentId;
......
...@@ -57,18 +57,18 @@ const Protocol = () => { ...@@ -57,18 +57,18 @@ const Protocol = () => {
return ( return (
<> <>
<Helmet> <Helmet>
<title>Zápis ze zasedání | CF 2021 | Pirátská strana</title> <title>Zápis ze zasedání | CF 2024 | Pirátská strana</title>
<meta <meta
name="description" name="description"
content="Interaktivní zápis z on-line zasedání Celostátního fóra České pirátské strany, 9. 1. 2021." content="Interaktivní zápis z on-line zasedání Celostátního fóra České pirátské strany, 13. 1. 2024."
/> />
<meta <meta
property="og:title" property="og:title"
content="Zápis ze zasedání | CF 2021 | Pirátská strana" content="Zápis ze zasedání | CF 2024 | Pirátská strana"
/> />
<meta <meta
property="og:description" property="og:description"
content="Interaktivní zápis z on-line zasedání Celostátního fóra České pirátské strany, 9. 1. 2021." content="Interaktivní zápis z on-line zasedání Celostátního fóra České pirátské strany, 13. 1. 2024."
/> />
</Helmet> </Helmet>
<article className="container container--default py-8 lg:py-24"> <article className="container container--default py-8 lg:py-24">
......