AddPostForm.jsx 7.41 KiB
import React, { useCallback, useRef, useState } from "react";
import useOutsideClick from "@rooks/use-outside-click";
import useTimeout from "@rooks/use-timeout";
import classNames from "classnames";
import { addPost, addProposal } from "actions/posts";
import Button from "components/Button";
import { Card, CardBody } from "components/cards";
import ErrorMessage from "components/ErrorMessage";
import MarkdownEditor from "components/mde/MarkdownEditor";
import { useActionState } from "hooks";
const AddPostForm = ({ className, canAddProposal }) => {
const cardRef = useRef();
const editorRef = useRef();
const [expanded, setExpanded] = useState(false);
const [text, setText] = useState("");
const [showAddConfirm, setShowAddConfirm] = useState(false);
const [type, setType] = useState("post");
const [error, setError] = useState(null);
const [addingPost, addingPostError] = useActionState(addPost, {
content: text,
});
const [addingProposal, addingProposalError] = useActionState(addProposal, {
content: text,
});
const apiError = addingPostError || addingProposalError;
const is429ApiError =
apiError &&
apiError.toString().indexOf("Unexpected status code 429") !== -1;
const onOutsideClick = useCallback(() => {
setExpanded(false);
}, [setExpanded]);
const onWrite = useCallback(
(evt) => {
setShowAddConfirm(false);
if (!expanded) {
setExpanded(true);
setTimeout(() => {
if (
editorRef.current &&
editorRef.current.finalRefs.textarea.current
) {
editorRef.current.finalRefs.textarea.current.focus();
}
}, 0);
}
},
[setExpanded, expanded, setShowAddConfirm]
);
const hideAddConfirm = useCallback(() => {
setShowAddConfirm(false);
}, [setShowAddConfirm]);
useOutsideClick(cardRef, onOutsideClick);
const { start: enqueueHideAddConfirm } = useTimeout(hideAddConfirm, 2000);
const onTextInput = (newText) => {
setText(newText);
if (newText !== "") {
if (newText.length >= 1024) {
setError("Maximální délka příspěvku je 1024 znaků.");
} else {
setError(null);
}
}
};
const onAdd = async (evt) => {
evt.preventDefault();
if (!!text) {
if (!error) {
const result = await (type === "post" ? addPost : addProposal).run({
content: text,
});
if (!result.error) {
setText("");
setExpanded(false);
setShowAddConfirm(true);
enqueueHideAddConfirm();
}
}
} else {
setError("Před přidáním příspěvku nezapomeňte vyplnit jeho obsah.");
}
};
const wrapperClass = classNames(
className,
"hover:elevation-16 transition duration-500",
{
"elevation-4 cursor-text": !expanded && !showAddConfirm,
"lg:elevation-16 container-padding--zero lg:container-padding--auto": expanded,
}
);
return (
<Card className={wrapperClass} ref={cardRef}>
<span
className={classNames("alert items-center transition duration-500", {
"alert--success": showAddConfirm,
"alert--light": !showAddConfirm,
hidden: expanded,
})}
onClick={onWrite}
>
<i
className={classNames("alert__icon text-lg mr-4", {
"ico--checkmark": showAddConfirm,
"ico--pencil": !showAddConfirm,
})}
/>
{showAddConfirm && <span>Příspěvek byl přidán.</span>}
{!showAddConfirm && <span>Napiš nový příspěvek ...</span>}
</span>
<CardBody
className={
"p-4 lg:p-8 " + (showAddConfirm || !expanded ? "hidden" : "")
}
>
<form className="space-y-4" onSubmit={onAdd}>
{apiError && is429ApiError && (
<div className="alert alert--warning">
<i className="alert__icon ico--clock text-lg" />
<span>
<strong>Zpomal!</strong> Další příspěvek můžeš přidat nejdříve
po 1 minutě od přidání posledního.
</span>
</div>
)}
{apiError && !is429ApiError && (
<ErrorMessage>
Při přidávání příspěvku došlo k problému: {apiError}.
</ErrorMessage>
)}
<MarkdownEditor
ref={editorRef}
value={text}
onChange={onTextInput}
error={error}
placeholder="Vyplňte text vašeho příspěvku"
toolbarCommands={[
["header", "bold", "italic", "strikethrough"],
["link", "quote"],
["unordered-list", "ordered-list"],
]}
/>
{canAddProposal && (
<div
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">
<label>
<input
type="radio"
name="postType"
value="post"
defaultChecked
/>
<span className="text-sm sm:text-base">
Přidávám <strong>běžný příspěvek</strong>
</span>
</label>
</div>
<div className="radio form-field__control ml-0 mt-4 sm:mt-0 sm:ml-4">
<label>
<input
type="radio"
name="postType"
value="procedure-proposal"
/>
<span className="text-sm sm:text-base">
Přidávám <strong>návrh postupu</strong>
</span>
</label>
</div>
</div>
</div>
)}
{type === "procedure-proposal" && (
<p className="alert alert--light text-sm">
<i className="alert__icon ico--info mr-2 text-lg hidden md:block" />
<span>
Návrh postupu se v rozpravě zobrazí až poté, co předsedající{" "}
<strong>posoudí jeho přijatelnost</strong>. Po odeslání proto
nepanikař, že jej hned nevidíš.
</span>
</p>
)}
<div className="space-x-4">
<Button
type="submit"
disabled={error || addingPost || addingProposal}
loading={addingPost || addingProposal}
fullwidth
hoverActive
className="text-sm xl:text-base"
>
{type === "post" && "Přidat příspěvek"}
{type === "procedure-proposal" && "Navrhnout postup"}
</Button>
<span className="text-sm text-grey-200 hidden lg:inline">
Pro pokročilejší formátování můžete používat{" "}
<a
href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet"
className="underline"
target="_blank"
rel="noreferrer noopener"
>
Markdown
</a>
.
</span>
</div>
</form>
</CardBody>
</Card>
);
};
export default AddPostForm;