Skip to content
Snippets Groups Projects
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;