Skip to content
Snippets Groups Projects
Forked from TO / cf-online-ui
44 commits behind the upstream repository.
Home.jsx 12.61 KiB
import React, { useCallback, useEffect, useState } from "react";
import { Helmet } from "react-helmet-async";
import Joyride, { EVENTS } from "react-joyride";
import ReactPlayer from "react-player/lazy";
import { useKeycloak } from "@react-keycloak/web";
import useWindowSize from "@rooks/use-window-size";

import {
  closeDiscussion,
  endProgramPoint,
  openDiscussion,
  renameProgramPoint,
} from "actions/program";
import { DropdownMenu, DropdownMenuItem } from "components/dropdown-menu";
import {
  AlreadyFinished,
  BreakInProgress,
  NotYetStarted,
} from "components/home";
import ModalConfirm from "components/modals/ModalConfirm";
import { Beacon, steps } from "components/onboarding";
import ProgramEntryEditModal from "components/program/ProgramEntryEditModal";
import AddAnnouncementForm from "containers/AddAnnouncementForm";
import AddPostForm from "containers/AddPostForm";
import AnnouncementsContainer from "containers/AnnoucementsContainer";
import GlobalStats from "containers/GlobalStats";
import JitsiInviteCard from "containers/JitsiInviteCard";
import PostFilters from "containers/PostFilters";
import PostsContainer from "containers/PostsContainer";
import { useActionConfirm } from "hooks";
import { AuthStore, GlobalInfoStore, ProgramStore } from "stores";

import "./Home.css";

const tourLSKey = "cf2021__tour";

const Home = () => {
  const {
    currentId,
    items: programEntries,
    scheduleIds,
  } = ProgramStore.useState();
  const { isAuthenticated, user } = AuthStore.useState();
  const { streamUrl } = GlobalInfoStore.useState();
  const programEntry = currentId ? programEntries[currentId] : null;
  const [showProgramEditModal, setShowProgramEditModal] = useState(false);
  const [runJoyRide, setRunJoyride] = useState(false);
  // The easiest way to restart the joyride tour is by simply re-rendering the component.
  const [joyrideRenderKey, setJoyrideRenderKey] = useState(0);
  const { innerWidth } = useWindowSize();
  const isLg = innerWidth >= 1024;
  const [
    showCloseDiscussion,
    setShowCloseDiscussion,
    onCloseDiscussionConfirm,
    onCloseDiscussionCancel,
  ] = useActionConfirm(closeDiscussion, programEntry);
  const [
    showOpenDiscussion,
    setShowOpenDiscussion,
    onOpenDiscussionConfirm,
    onOpenDiscussionCancel,
  ] = useActionConfirm(openDiscussion, programEntry);
  const [
    showEndProgramPoint,
    setShowEndProgramPoint,
    onEndProgramPointConfirm,
    onEndProgramPointCancel,
  ] = useActionConfirm(endProgramPoint, programEntry);
  const { keycloak } = useKeycloak();

  const login = useCallback(() => {
    keycloak.login();
  }, [keycloak]);

  useEffect(() => {
    if (isLg && !localStorage.getItem(tourLSKey)) {
      setRunJoyride(true);
    }
  }, [isLg, setRunJoyride]);

  const onEditProgramConfirm = async (newTitle) => {
    await renameProgramPoint.run({ programEntry, newTitle });
    setShowProgramEditModal(false);
  };
  const onEditProgramCancel = () => {
    setShowProgramEditModal(false);
  };

  const showTutorial = useCallback(() => {
    setRunJoyride(true);
    setJoyrideRenderKey(joyrideRenderKey + 1);
  }, [joyrideRenderKey, setRunJoyride, setJoyrideRenderKey]);

  const handleJoyrideCallback = ({ action, index, status, type }) => {
    if (type === EVENTS.TOUR_END) {
      localStorage.setItem(tourLSKey, "COMPLETED");
    }
  };

  const firstProgramEntry = scheduleIds.length
    ? programEntries[scheduleIds[0]]
    : null;

  const lastProgramEntry = scheduleIds.length
    ? programEntries[scheduleIds[0]]
    : null;

  if (
    !programEntry &&
    (!firstProgramEntry || new Date() < firstProgramEntry.expectedStartAt)
  ) {
    return (
      <NotYetStarted
        startAt={
          firstProgramEntry ? firstProgramEntry.expectedStartAt : undefined
        }
      />
    );
  }

  if (
    !programEntry &&
    lastProgramEntry &&
    new Date() > lastProgramEntry.expectedStartAt
  ) {
    return <AlreadyFinished />;
  }

  if (!programEntry) {
    return <BreakInProgress />;
  }

  const displayActions = isAuthenticated && user.role === "chairman";

  return (
    <>
      <Helmet>
        <title>Přímý přenos | CF 2021 | Pirátská strana</title>
        <meta
          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."
        />
        <meta
          property="og:title"
          content="Přímý přenos | CF 2021 | Pirátská strana"
        />
        <meta
          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."
        />
      </Helmet>
      <Joyride
        beaconComponent={Beacon}
        continuous={true}
        locale={{
          back: "Zpět",
          close: "Zavřít",
          last: "Poslední",
          next: "Další",
          skip: "Přeskočit intro",
        }}
        key={joyrideRenderKey}
        run={runJoyRide}
        showProgress={true}
        showSkipButton={true}
        scrollToFirstStep={true}
        callback={handleJoyrideCallback}
        steps={steps}
        styles={{
          options: {
            arrowColor: "#fff",
            backgroundColor: "#fff",
            overlayColor: "rgba(255, 255, 255, 0.75)",
            primaryColor: "#000",
            textColor: "#000",
            textAlign: "left",
            outline: "none",
            zIndex: 1000,
            borderRadius: 0,
          },
          tooltip: {
            borderRadius: 0,
          },
          tooltipContent: {
            textAlign: "left",
          },
          buttonClose: {
            borderRadius: 0,
            fontSize: "0.875rem",
          },
          buttonNext: {
            borderRadius: 0,
            padding: ".75em 2em",
            fontSize: "0.875rem",
          },
          buttonBack: {
            color: "#4c4c4c",
            fontSize: "0.875rem",
          },
          buttonSkip: {
            color: "#4c4c4c",
            fontSize: "0.875rem",
          },
        }}
      />
      <article className="container container--wide py-8 lg:py-24 cf2021 bg-white">
        <div className="cf2021__title flex justify-between">
          <h1 className="head-alt-base lg:head-alt-lg">
            {programEntry.number !== "" && `Bod č. ${programEntry.number}: `}
            {programEntry.title}
          </h1>
          <div className="pl-4 pt-1 lg:pt-5">
            <div className="space-x-4 inline-flex items-center">
              <button
                className="ico--question text-grey-200 hidden lg:block hover:text-black text-lg"
                aria-label="Potřebuješ pomoc? Spusť si znovu nápovědu jak tuhle aplikaci používat."
                data-tip="Potřebuješ pomoc? Spusť si znovu nápovědu jak tuhle aplikaci používat."
                data-tip-at="top"
                onClick={showTutorial}
              />
              {displayActions && (
                <DropdownMenu right triggerSize="lg">
                  <DropdownMenuItem
                    onClick={() => setShowProgramEditModal(true)}
                    icon="ico--pencil"
                    title="Přejmenovat bod programu"
                    titleSize="base"
                    iconSize="base"
                  />
                  {programEntry.discussionOpened && (
                    <DropdownMenuItem
                      onClick={() => setShowCloseDiscussion(true)}
                      icon="ico--bubbles"
                      title="Ukončit rozpravu"
                      titleSize="base"
                      iconSize="base"
                    />
                  )}
                  {!programEntry.discussionOpened && (
                    <DropdownMenuItem
                      onClick={() => setShowOpenDiscussion(true)}
                      icon="ico--bubbles"
                      title="Otevřít rozpravu"
                      titleSize="base"
                      iconSize="base"
                    />
                  )}
                  <DropdownMenuItem
                    onClick={() => setShowEndProgramPoint(true)}
                    icon="ico--switch"
                    title="Ukončit bod programu"
                    titleSize="base"
                    iconSize="base"
                  />
                </DropdownMenu>
              )}
            </div>
          </div>
        </div>
        <section className="cf2021__video">
          <div className="container-padding--zero md:container-padding--auto">
            {streamUrl && (
              <div className="iframe-container joyride-player">
                <ReactPlayer
                  url={streamUrl}
                  title="Video stream"
                  controls={true}
                  playing={true}
                  muted={true}
                  width="100%"
                  height=""
                />
              </div>
            )}
            {!streamUrl && (
              <p>
                Server neposlal informaci o aktuálním streamu. Vyčkejte na
                aktualizaci.
              </p>
            )}
            <GlobalStats />
          </div>
        </section>

        <section className="cf2021__notifications space-y-8">
          <JitsiInviteCard />

          <div className="lg:card lg:elevation-10 joyride-announcements">
            <AnnouncementsContainer className="container-padding--zero lg:container-padding--auto" />
            {isAuthenticated && user.role === "chairman" && (
              <AddAnnouncementForm className="lg:card__body pt-4 lg:py-6" />
            )}
          </div>
        </section>

        <section className="cf2021__posts joyride-posts">
          <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">
              <span>Příspěvky v rozpravě</span>
            </h2>
            <PostFilters />
          </div>

          {!programEntry.discussionOpened &&
            (!isAuthenticated || (isAuthenticated && !user.isBanned)) && (
              <p className="alert alert--light items-center mb-4 elevation-4">
                <i className="alert__icon ico--lock text-lg" />
                Rozprava je uzavřena - příspěvky teď nelze přidávat.
              </p>
            )}
          {programEntry.discussionOpened && !isAuthenticated && (
            <p className="alert alert--light items-center mb-4">
              <i className="alert__icon ico--info text-lg" />
              <span>
                Pokud chceš přidat nový příspěvek,{"  "}
                <button onClick={login} className="underline cursor-pointer">
                  přihlaš se pomocí Pirátské identity
                </button>
                .
              </span>
            </p>
          )}
          {programEntry.discussionOpened && isAuthenticated && user.isBanned && (
            <p className="alert alert--error items-center mb-4">
              <i className="alert__icon ico--warning text-lg" />
              Jejda! Nemůžeš přidávat příspěvky, protože máš ban. Vyčkej než ti
              ho předsedající odebere.
            </p>
          )}
          {programEntry.discussionOpened &&
            isAuthenticated &&
            !user.isBanned && <AddPostForm className="mb-8" canAddProposal={user.role === "member" || user.role === "chairman"} />}

          <PostsContainer
            className="container-padding--zero lg:container-padding--auto"
            showAddPostCta={programEntry.discussionOpened}
          />
        </section>
      </article>
      <ProgramEntryEditModal
        isOpen={showProgramEditModal}
        onConfirm={onEditProgramConfirm}
        onCancel={onEditProgramCancel}
        programEntry={programEntry}
      />
      <ModalConfirm
        isOpen={showCloseDiscussion}
        onConfirm={onCloseDiscussionConfirm}
        onCancel={onCloseDiscussionCancel}
        title="Ukončit rozpravu?"
        yesActionLabel="Ukončit"
      >
        Opravdu chcete ukončit rozpravu?
      </ModalConfirm>
      <ModalConfirm
        isOpen={showOpenDiscussion}
        onConfirm={onOpenDiscussionConfirm}
        onCancel={onOpenDiscussionCancel}
        title="Otevřít rozpravu?"
        yesActionLabel="Otevřít"
      >
        Opravdu chcete otevřít rozpravu k tomuto bodu programu?
      </ModalConfirm>
      <ModalConfirm
        isOpen={showEndProgramPoint}
        onConfirm={onEndProgramPointConfirm}
        onCancel={onEndProgramPointCancel}
        title="Ukončit bod programu?"
        yesActionLabel="Ukončit bod programu"
      >
        Bod programu <strong>bude ukončen</strong>. Opravdu to chcete?
      </ModalConfirm>
    </>
  );
};

export default Home;