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

Target

Select target project
  • to/cf-online-ui
  • vpfafrin/cf2021
2 results
Select Git revision
Show changes
Showing
with 643 additions and 0 deletions
import React, { useState } from "react";
import classNames from "classnames";
import Button from "components/Button";
import { Card, CardActions, CardBody, CardHeadline } from "components/cards";
import ErrorMessage from "components/ErrorMessage";
import MarkdownEditor from "components/mde/MarkdownEditor";
import Modal from "components/modals/Modal";
import { urlRegex } from "utils";
const AnnouncementEditModal = ({
announcement,
onCancel,
onConfirm,
confirming,
error,
...props
}) => {
const [text, setText] = useState(announcement.content);
const [link, setLink] = useState(announcement.link);
const [textError, setTextError] = useState(null);
const [linkError, setLinkError] = useState(null);
const onTextInput = (newText) => {
setText(newText);
if (newText !== "") {
if (newText.length > 1024) {
setTextError("Maximální délka příspěvku je 1024 znaků.");
} else {
setTextError(null);
}
}
};
const onLinkInput = (newLink) => {
setLink(newLink);
if (!!newLink) {
if (newLink.length > 1024) {
setLinkError("Maximální délka URL je 256 znaků.");
} else {
setLinkError(urlRegex.test(newLink) ? null : "Zadejte platnou URL.");
}
}
};
const confirm = (evt) => {
evt.preventDefault();
let preventAction = false;
const payload = {
content: text,
};
if (!text) {
setTextError("Před úpravou oznámení nezapomeňte vyplnit jeho obsah.");
preventAction = true;
} else if (!!text && text.length > 1024) {
setTextError("Maximální délka oznámení je 1024 znaků.");
preventAction = true;
}
if (announcement.type === "voting" && !link) {
setLinkError("Zadejte platnou URL.");
preventAction = true;
} else {
payload.link = link;
}
if (preventAction) {
return;
}
onConfirm(payload);
};
return (
<Modal containerClassName="max-w-lg" onRequestClose={onCancel} {...props}>
<form onSubmit={confirm}>
<Card className="elevation-21">
<CardBody>
<div className="flex items-center justify-between mb-4">
<CardHeadline>Upravit oznámení</CardHeadline>
<button onClick={onCancel} type="button">
<i className="ico--cross"></i>
</button>
</div>
<MarkdownEditor
value={text}
onChange={onTextInput}
error={textError}
placeholder="Vyplňte text oznámení"
toolbarCommands={[
["bold", "italic", "strikethrough"],
["link", "unordered-list", "ordered-list"],
]}
/>
<div
className={classNames("form-field mt-4", {
hidden: announcement.type !== "voting",
"form-field--error": !!linkError,
})}
>
<div className="form-field__wrapper form-field__wrapper--shadowed">
<input
type="text"
className="text-input text-sm text-input--has-addon-l form-field__control"
value={link}
placeholder="URL hlasování"
onChange={(evt) => onLinkInput(evt.target.value)}
/>
<div className="text-input-addon text-input-addon--l order-first">
<i className="ico--link"></i>
</div>
</div>
{!!linkError && (
<div className="form-field__error">{linkError}</div>
)}
</div>
{error && (
<ErrorMessage className="mt-2">
Při editaci došlo k problému: {error}
</ErrorMessage>
)}
</CardBody>
<CardActions right className="space-x-1">
<Button
hoverActive
color="blue-300"
className="text-sm"
loading={confirming}
disabled={textError || linkError || confirming}
type="submit"
>
Uložit
</Button>
<Button
hoverActive
color="red-600"
className="text-sm"
onClick={onCancel}
type="button"
>
Zrušit
</Button>
</CardActions>
</Card>
</form>
</Modal>
);
};
export default AnnouncementEditModal;
import React from "react";
import classNames from "classnames";
import Announcement from "./Announcement";
const AnnouncementList = ({
items,
className,
canRunActions,
onDelete,
onEdit,
onSeen,
}) => {
const buildHandler = (responderFn) => (announcement) => (evt) => {
evt.preventDefault();
responderFn(announcement);
};
const onAnnouncementEdit = buildHandler(onEdit);
const onAnnouncementDelete = buildHandler(onDelete);
const onAnnouncementSeen = (announcement) => () => {
onSeen(announcement);
};
const getClassName = (idx) => {
if (idx === 0) {
return "pt-4 lg:pt-8";
}
if (idx === items.length - 1) {
return "pb-4 lg:pb-8";
}
return "";
};
return (
<div className={classNames("space-y-px", className)}>
{items.map((item, idx) => (
<Announcement
className={getClassName(idx)}
key={item.id}
datetime={item.datetime}
type={item.type}
content={item.contentHtml}
link={item.link}
seen={item.seen}
canRunActions={canRunActions}
onEdit={onAnnouncementEdit(item)}
onDelete={onAnnouncementDelete(item)}
onSeen={onAnnouncementSeen(item)}
/>
))}
{!items.length && (
<p className="px-8 py-4 leading-snug text-sm md:text-base">
Zatím žádná oznámení.
</p>
)}
</div>
);
};
export default AnnouncementList;
import React from "react";
import classNames from "classnames";
const Card = ({ children, className }, ref) => {
const cls = classNames("card", className);
return (
<div className={cls} ref={ref}>
{children}
</div>
);
};
export default React.forwardRef(Card);
import React from "react";
import classNames from "classnames";
const CardActions = ({ children, right, className }) => {
const cls = classNames(
"card-actions",
{ "card-actions--right": !!right },
className
);
return <div className={cls}>{children}</div>;
};
export default CardActions;
import React from "react";
import classNames from "classnames";
const CardBody = ({ children, className, ...props }) => {
const cls = classNames("card__body", className);
return (
<div className={cls} {...props}>
{children}
</div>
);
};
export default CardBody;
import React from "react";
const CardBodyText = ({ children }) => {
return <div className="card-body-text">{children}</div>;
};
export default CardBodyText;
import React from "react";
const CardHeadline = ({ children }) => {
return <h1 className="card-headline">{children}</h1>;
};
export default CardHeadline;
export { default as Card } from "./Card";
export { default as CardActions } from "./CardActions";
export { default as CardBody } from "./CardBody";
export { default as CardBodyText } from "./CardBodyText";
export { default as CardHeadline } from "./CardHeadline";
import React from "react";
import classNames from "classnames";
const DropdownButton = ({
className,
color = "black",
hoverActive = true,
fullwidth = false,
loading = false,
disabled = false,
items,
children,
onClick,
...props
}) => {
const btnClass = classNames(
"btn btn--icon",
`btn--${color}`,
{
"btn--hoveractive": hoverActive,
"btn--fullwidth md:btn--autowidth": fullwidth,
"btn--loading": loading,
},
className
);
const inner = (
<div className="btn__body-wrap">
<button className="btn__body" onClick={onClick} disabled={disabled}>
{children}
</button>
<button className="btn__icon dropdown-button">
<i className="ico--chevron-down"></i>
<ul className="dropdown-button__choices bg-white text-black whitespace-no-wrap">
{items}
</ul>
</button>
</div>
);
return (
<div className={btnClass} {...props}>
{inner}
</div>
);
};
export default DropdownButton;
import React from "react";
const DropdownButtonItem = ({ onClick, children }) => {
return (
<li className="dropdown-button__choice hover:bg-grey-125" onClick={onClick}>
<span className="block px-4 py-3">{children}</span>
</li>
);
};
export default DropdownButtonItem;
export { default as DropdownButton } from "./DropdownButton";
export { default as DropdownButtonItem } from "./DropdownButtonItem";
import React from "react";
import classNames from "classnames";
const DropdownMenu = ({
children,
className,
right,
triggerSize = "sm",
triggerIconClass = "ico--dots-three-vertical",
}) => {
const wrapperCls = classNames(
"dropdown",
{
"dropdown--right": !!right,
},
className
);
const triggerCls = classNames(
"cursor-pointer ml-auto text-grey-200 hover:text-black",
`text-${triggerSize}`,
triggerIconClass
);
return (
<div className={wrapperCls}>
<i className={triggerCls}></i>
<ul className="dropdown__content whitespace-no-wrap">{children}</ul>
</div>
);
};
export default React.memo(DropdownMenu);
import React from "react";
import classNames from "classnames";
const DropdownMenuItem = ({
icon,
className,
onClick,
title,
titleClass,
iconSize = "2xs",
titleSize = "xs",
}) => {
const iconCls = classNames("text-2xs mr-2", `text-${iconSize}`, icon);
const titleCls = classNames(`text-${titleSize}`, titleClass);
const cls = classNames(
"dropdown__content-item bg-white hover:bg-grey-125 cursor-pointer",
className
);
return (
<li className={cls} onClick={onClick}>
<span className="block px-3 py-3">
<i className={iconCls}></i>
<span className={titleCls}>{title}</span>
</span>
</li>
);
};
export default React.memo(DropdownMenuItem);
export { default as DropdownMenu } from "./DropdownMenu";
export { default as DropdownMenuItem } from "./DropdownMenuItem";
import React from "react";
const AlreadyFinished = () => (
<article className="container container--wide py-8 md:py-16 lg:py-32">
<div className="flex">
<div>
<i className="ico--anchor text-2xl md:text-6xl lg:text-9xl mr-4 lg:mr-8"></i>
</div>
<div>
<h1 className="head-alt-base md:head-alt-md lg:head-alt-xl mb-2">
Jednání už skočilo!
</h1>
<p className="text-xl leading-snug">
Oficiální program již skončil. Těšíme se na viděnou zase příště.
</p>
</div>
</div>
</article>
);
export default AlreadyFinished;
import React from "react";
import Button from "components/Button";
const BreakInProgress = () => (
<article className="container container--wide py-8 md:py-16 lg:py-32">
<div className="flex">
<div>
<i className="ico--clock text-2xl md:text-6xl lg:text-9xl mr-4 lg:mr-8"></i>
</div>
<div>
<h1 className="head-alt-base md:head-alt-md lg:head-alt-xl mb-2">
Probíhá přestávka ...
</h1>
<p className="text-xl leading-snug mb-8">
Jednání celostátního fóra je momentálně přerušeno. Můžete si ale
zobrazit program.
</p>
<Button
routerTo="/program"
className="md:text-lg lg:text-xl"
hoverActive
>
Zobrazit program
</Button>
</div>
</div>
</article>
);
export default BreakInProgress;
import React from "react";
import { format } from "date-fns";
import Button from "components/Button";
const NotYetStarted = ({ startAt }) => (
<article className="container container--wide py-8 md:py-16 lg:py-32">
<div className="hidden md:inline-block flag bg-violet-400 text-white head-alt-base mb-4 py-4 px-5">
Jejda ...
</div>
<h1 className="head-alt-base md:head-alt-md lg:head-alt-xl mb-2">
Jednání ještě nebylo zahájeno
</h1>
<p className="text-xl leading-snug mb-8">
<span>Jednání celostátního fóra ještě nezačalo. </span>
{startAt && (
<span>
Mělo by být zahájeno <strong>{format(startAt, "d. M. Y")}</strong> v{" "}
<strong>{format(startAt, "H:mm")}</strong>.{" "}
</span>
)}
<span>Můžete si ale zobrazit program.</span>
</p>
<Button routerTo="/program" className="md:text-lg lg:text-xl" hoverActive>
Zobrazit program
</Button>
</article>
);
export default NotYetStarted;
export { default as AlreadyFinished } from "./AlreadyFinished";
export { default as BreakInProgress } from "./BreakInProgress";
export { default as NotYetStarted } from "./NotYetStarted";
.mde-header {
background: transparent;
}
.react-mde .invisible {
display: none;
}
.mde-header {
border: 0;
align-items: center;
}
.mde-header .mde-tabs {
display: inline-flex;
background-color: #000;
background-color: rgba(0,0,0,1);
padding: .25rem;
}
.mde-header .mde-tabs button {
padding: .25rem 0.25rem;
font-family: Bebas Neue,Helvetica,Arial,sans-serif;
font-weight: 400;
font-size: 1.1rem;
--text-opacity: 1;
color: #fff;
color: rgba(255,255,255,var(--text-opacity));
text-align: center;
cursor: pointer;
border: 0;
border-radius: 0;
margin: 0 !important;
outline: 0;
}
.mde-header {
/* grey-50 */
background-color: #f7f7f7;
}
.mde-header .mde-tabs button.selected {
/* blue-300 */
background: #027da8;
color: #fff;
border: 0;
border-radius: 0;
margin: 0;
outline: 0;
}
.mde-header .mde-tabs button:not(.selected):hover {
/* grey-500 */
background: #303132;
}
.mde-header .mde-header-item {
border: 1px transparent solid;
transition: border-color 100ms ease-in-out;
}
.mde-header ul.mde-header-group li.mde-header-item {
margin: 0;
}
.mde-header .mde-header-item:hover {
/* grey-200 */
border: 1px #adadad solid;
}
.mde-text {
font-family: monospace;
}
.mde-header ul.mde-header-group {
padding: 0;
}
.mde-header ul.mde-header-group:first-of-type {
padding-left: .5rem;
}
.mde-header ul.mde-header-group + .mde-header-group {
margin-left: 0;
}
@media (min-width: 992px) {
.mde-header .mde-tabs button {
padding: .5rem 1rem;
}
}
@media (min-width: 1200px) {
.mde-header ul.mde-header-group {
padding: 0 0.5rem;
}
.mde-header ul.mde-header-group + .mde-header-group {
margin-left: .5rem;
}
}
import React, { useState } from "react";
import ReactMde from "react-mde";
import classNames from "classnames";
import { markdownConverter } from "markdown";
import "react-mde/lib/styles/css/react-mde-toolbar.css";
import "./MarkdownEditor.css";
const MarkdownEditor = (
{ value, onChange, error, placeholder = "", ...props },
ref
) => {
const [selectedTab, setSelectedTab] = useState("write");
const classes = {
preview: "p-2 content-block text-input text-sm md:text-base",
textArea: "p-2 text-input text-sm md:text-base",
};
const l18n = {
write: "Psaní",
preview: "Náhled",
uploadingImage: "Nahrávám obrázek",
};
const childProps = {
textArea: {
placeholder,
},
};
return (
<div className={classNames("form-field", { "form-field--error": !!error })}>
<ReactMde
ref={ref}
value={value}
onChange={onChange}
selectedTab={selectedTab}
onTabChange={setSelectedTab}
generateMarkdownPreview={(markdown) =>
Promise.resolve(markdownConverter.makeHtml(markdown))
}
classes={classes}
l18n={l18n}
childProps={childProps}
{...props}
/>
{error && <div className="form-field__error">{error}</div>}
</div>
);
};
export default React.forwardRef(MarkdownEditor);