Skip to content
Snippets Groups Projects
Commit 0185c9c9 authored by xaralis's avatar xaralis
Browse files

feat: edit announcement link, polishing

parent 7cb67a1f
No related branches found
No related tags found
No related merge requests found
......@@ -65,7 +65,7 @@ const onKeycloakEvent = (event) => {
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 md:mb-4">
<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"
......@@ -73,7 +73,7 @@ const LoadingComponent = (
/>
<h1 className="head-alt-md md:head-alt-lg">Celostátní fórum 2021</h1>
</div>
<p className="text-center">Načítám aplikaci ...</p>
<p className="text-center head-xs md:head-base">Načítám aplikaci ...</p>
</div>
</div>
);
......
......@@ -82,25 +82,23 @@ export const deleteAnnouncement = createAsyncAction(
);
/**
* Update content of an announcement.
* Update an announcement.
*/
export const updateAnnouncementContent = createAsyncAction(
export const updateAnnouncement = createAsyncAction(
/**
*
* @param {CF2021.Announcement} item
* @param {string} newContent
* @param {Object} payload
*/
async ({ item, newContent }) => {
async ({ item, payload }) => {
try {
const body = JSON.stringify({
content: newContent,
});
const body = JSON.stringify(payload);
await fetch(`/announcements/${item.id}`, {
method: "PUT",
body,
expectedStatus: 204,
});
return successResult({ item, newContent });
return successResult({ item, payload });
} catch (err) {
return errorResult([], err.toString());
}
......
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,
......@@ -15,6 +17,8 @@ const AnnouncementEditModal = ({
...props
}) => {
const [text, setText] = useState(announcement.content);
const [link, setLink] = useState(announcement.link);
const [linkValid, setLinkValid] = useState(null);
const [noTextError, setNoTextError] = useState(false);
const onTextInput = (newText) => {
......@@ -25,64 +29,116 @@ const AnnouncementEditModal = ({
}
};
const onLinkInput = (newLink) => {
setLink(newLink);
if (!!newLink) {
setLinkValid(urlRegex.test(newLink));
}
};
const confirm = (evt) => {
if (!!text) {
onConfirm(text);
} else {
evt.preventDefault();
let preventAction = false;
const payload = {
content: text,
};
if (!text) {
setNoTextError(true);
preventAction = true;
}
if (announcement.type === "voting" && !link) {
setLinkValid(false);
preventAction = true;
} else {
payload.link = link;
}
if (preventAction) {
return;
}
onConfirm(payload);
};
return (
<Modal containerClassName="max-w-lg" onRequestClose={onCancel} {...props}>
<Card>
<CardBody>
<div className="flex items-center justify-between mb-4">
<CardHeadline>Upravit oznámení</CardHeadline>
<button onClick={onCancel}>
<i className="ico--close"></i>
</button>
</div>
<MarkdownEditor
value={text}
onChange={onTextInput}
error={
noTextError
? "Před úpravou oznámení nezapomeňte vyplnit jeho obsah."
: null
}
placeholder="Vyplňte text oznámení"
toolbarCommands={[
["bold", "italic", "strikethrough"],
["link", "unordered-list", "ordered-list"],
]}
/>
{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}
onClick={confirm}
>
Uložit
</Button>
<Button
hoverActive
color="red-600"
className="text-sm"
onClick={onCancel}
>
Zrušit
</Button>
</CardActions>
</Card>
<form onSubmit={confirm}>
<Card>
<CardBody>
<div className="flex items-center justify-between mb-4">
<CardHeadline>Upravit oznámení</CardHeadline>
<button onClick={onCancel} type="button">
<i className="ico--close"></i>
</button>
</div>
<MarkdownEditor
value={text}
onChange={onTextInput}
error={
noTextError
? "Před úpravou oznámení nezapomeňte vyplnit jeho obsah."
: null
}
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": linkValid === false,
})}
>
<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--link1"></i>
</div>
</div>
{linkValid === false && (
<div className="form-field__error">Zadejte platnou URL.</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}
type="submit"
>
Uložit
</Button>
<Button
hoverActive
color="red-600"
className="text-sm"
onClick={onCancel}
type="button"
>
Zrušit
</Button>
</CardActions>
</Card>
</form>
</Modal>
);
};
......
......@@ -29,7 +29,7 @@ const ModalConfirm = ({
<CardBody>
<div className="flex items-center justify-between mb-4">
<CardHeadline>{title}</CardHeadline>
<button onClick={onCancel}>
<button onClick={onCancel} type="button">
<i className="ico--close"></i>
</button>
</div>
......
......@@ -26,6 +26,8 @@ const PostEditModal = ({
};
const confirm = (evt) => {
evt.preventDefault();
if (!!text) {
onConfirm(text);
} else {
......@@ -35,55 +37,57 @@ const PostEditModal = ({
return (
<Modal containerClassName="max-w-xl" onRequestClose={onCancel} {...props}>
<Card>
<CardBody>
<div className="flex items-center justify-between mb-4">
<CardHeadline>Upravit text příspěvku</CardHeadline>
<button onClick={onCancel}>
<i className="ico--close"></i>
</button>
</div>
<MarkdownEditor
value={text}
onChange={onTextInput}
error={
noTextError
? "Před upravením příspěvku nezapomeňte vyplnit jeho obsah."
: null
}
placeholder="Vyplňte text příspěvku"
toolbarCommands={[
["header", "bold", "italic", "strikethrough"],
["link", "quote", "image"],
["unordered-list", "ordered-list"],
]}
/>
{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}
onClick={confirm}
>
Uložit
</Button>
<Button
hoverActive
color="red-600"
className="text-sm"
onClick={onCancel}
>
Zrušit
</Button>
</CardActions>
</Card>
<form onSubmit={confirm}>
<Card>
<CardBody>
<div className="flex items-center justify-between mb-4">
<CardHeadline>Upravit text příspěvku</CardHeadline>
<button onClick={onCancel} type="button">
<i className="ico--close"></i>
</button>
</div>
<MarkdownEditor
value={text}
onChange={onTextInput}
error={
noTextError
? "Před upravením příspěvku nezapomeňte vyplnit jeho obsah."
: null
}
placeholder="Vyplňte text příspěvku"
toolbarCommands={[
["header", "bold", "italic", "strikethrough"],
["link", "quote", "image"],
["unordered-list", "ordered-list"],
]}
/>
{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}
onClick={confirm}
>
Uložit
</Button>
<Button
hoverActive
color="red-600"
className="text-sm"
onClick={onCancel}
>
Zrušit
</Button>
</CardActions>
</Card>
</form>
</Modal>
);
};
......
......@@ -6,8 +6,7 @@ import Button from "components/Button";
import ErrorMessage from "components/ErrorMessage";
import MarkdownEditor from "components/mde/MarkdownEditor";
import { useActionState } from "hooks";
const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
import { urlRegex } from "utils";
const AddAnnouncementForm = ({ className }) => {
const [text, setText] = useState("");
......@@ -30,30 +29,44 @@ const AddAnnouncementForm = ({ className }) => {
}
};
const onLinkInput = (evt) => {
setLink(evt.target.value);
const onLinkInput = (newLink) => {
setLink(newLink);
if (!!evt.target.value) {
setLinkValid(!!evt.target.value.match(urlRegex));
if (!!newLink) {
setLinkValid(urlRegex.test(newLink));
}
};
const onAdd = async (evt) => {
if (!link) {
let preventAction = false;
const payload = {
content: text,
type,
};
if (!text) {
setNoTextError(true);
preventAction = true;
}
if (type === "voting" && !link) {
setLinkValid(false);
preventAction = true;
} else {
payload.link = link;
}
if (!!text) {
if (type === "voting" && link && linkValid) {
const result = await addAnnouncement.run({ content: text, link, type });
if (preventAction) {
return;
}
if (!result.error) {
setText("");
setLink("");
}
}
} else {
setNoTextError(true);
const result = await addAnnouncement.run({ content: text, link, type });
if (!result.error) {
setText("");
setLink("");
setNoTextError(false);
setLinkValid(null);
}
};
......@@ -119,7 +132,7 @@ const AddAnnouncementForm = ({ className }) => {
className="text-input text-sm text-input--has-addon-l form-field__control"
value={link}
placeholder="URL hlasování"
onChange={onLinkInput}
onChange={(evt) => onLinkInput(evt.target.value)}
/>
<div className="text-input-addon text-input-addon--l order-first">
<i className="ico--link1"></i>
......
......@@ -4,7 +4,7 @@ import {
deleteAnnouncement,
loadAnnouncements,
markSeen,
updateAnnouncementContent,
updateAnnouncement,
} from "actions/announcements";
import AnnouncementEditModal from "components/annoucements/AnnouncementEditModal";
import AnnouncementList from "components/annoucements/AnnouncementList";
......@@ -14,7 +14,7 @@ import ModalConfirm from "components/modals/ModalConfirm";
import { useActionState, useItemActionConfirm } from "hooks";
import { AnnouncementStore, AuthStore } from "stores";
const AnnoucementsContainer = () => {
const AnnoucementsContainer = ({ className }) => {
const [itemToEdit, setItemToEdit] = useState(null);
const [confirmingEdit, setConfirmingEdit] = useState(false);
const [editError, setEditError] = useState(null);
......@@ -38,13 +38,13 @@ const AnnoucementsContainer = () => {
);
const confirmEdit = useCallback(
async (newContent) => {
if (itemToEdit && newContent) {
async (payload) => {
if (itemToEdit && payload) {
setConfirmingEdit(true);
const result = await updateAnnouncementContent.run({
const result = await updateAnnouncement.run({
item: itemToEdit,
newContent,
payload,
});
if (!result.error) {
......@@ -65,7 +65,7 @@ const AnnoucementsContainer = () => {
}, [setItemToEdit]);
return (
<>
<div className={className}>
{loadResult && loadResult.error && (
<CardBody>
<ErrorMessage>
......@@ -101,7 +101,7 @@ const AnnoucementsContainer = () => {
error={editError}
/>
)}
</>
</div>
);
};
......
......@@ -156,11 +156,15 @@ const Home = () => {
<article className="container container--wide pt-8 lg:py-24 cf2021">
<section className="cf2021__video">
<div className="flex justify-between mb-4 lg:mb-8">
<h1 className="head-alt-md lg:head-alt-lg">
<h1 className="head-alt-base lg:head-alt-lg">
Bod č. {programEntry.number}: {programEntry.title}
</h1>
{displayActions && (
<DropdownMenu right triggerSize="lg" className="pl-4 pt-5">
<DropdownMenu
right
triggerSize="lg"
className="pl-4 pt-1 lg:pt-5"
>
<DropdownMenuItem
onClick={() => setShowProgramEditModal(true)}
icon="ico--edit-pencil"
......@@ -224,7 +228,7 @@ const Home = () => {
<section className="cf2021__notifications">
<div className="lg:card lg:elevation-10">
<div className="lg:card__body pb-2 lg:py-6">
<h2 className="head-heavy-sm">Oznámení</h2>
<h2 className="head-heavy-xs md:head-heavy-sm">Oznámení</h2>
</div>
<AnnouncementsContainer className="container-padding--zero lg:container-padding--auto" />
......@@ -236,7 +240,7 @@ const Home = () => {
<section className="cf2021__posts">
<div className="flex flex-col xl:flex-row xl:justify-between xl:items-center mb-4">
<h2 className="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>
{!programEntry.discussionOpened && (
<i
......
......@@ -7,6 +7,7 @@ import WaitQueue from "wait-queue";
import { markdownConverter } from "markdown";
export const urlRegex = /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i;
export const seenPostsLSKey = "cf2021_seen_posts";
export const seenAnnouncementsLSKey = "cf2021_seen_announcements";
......
......@@ -9,6 +9,9 @@ export const handleAnnouncementChanged = (payload) => {
if (has(payload, "content")) {
state.items[payload.id].content = payload.content;
}
if (has(payload, "link")) {
state.items[payload.id].link = payload.link;
}
}
});
};
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment