hexlet-codebattle/codebattle

View on GitHub
services/app/apps/codebattle/assets/js/widgets/pages/builder/BuilderActions.jsx

Summary

Maintainability
D
2 days
Test Coverage
import React, { useCallback, useContext } from 'react';

import NiceModal from '@ebay/nice-modal-react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useDispatch, useSelector } from 'react-redux';

import RoomContext from '../../components/RoomContext';
import ModalCodes from '../../config/modalCodes';
import { taskStateCodes } from '../../config/task';
import {
  isIdleStateTaskSelector,
  isInvalidTaskSelector,
  isSavedTaskSelector,
  isTaskPrepareSavingSelector,
  isTaskPrepareTestingSelector,
  taskStateSelector,
} from '../../machines/selectors';
import {
  buildTaskAsserts,
  deleteTask,
  publishTask,
  updateTaskState,
} from '../../middlewares/Room';
import * as selectors from '../../selectors';
import useMachineStateSelector from '../../utils/useMachineStateSelector';

import { modalModes, modalActions } from './TaskParamsModal';

function ModerationActions({
  canPublish,
  canUnpublish,
  handlePublishTask,
  handleUnpublishTask,
}) {
  return (
    <>
      <button
        type="button"
        className="btn btn-md btn-outline-danger text-nowrap rounded-top"
        disabled
      >
        On moderation
      </button>
      {canUnpublish && (
        <button
          title="Cancel moderation"
          type="button"
          className="btn btn-md btn-primary text-nowrap rounded-bottom mb-2"
          onClick={handleUnpublishTask}
        >
          <FontAwesomeIcon className="mr-2" icon="share" />
          Cancel
        </button>
      )}
      {canPublish && (
        <button
          type="button"
          className="btn btn-md btn-primary text-nowrap rounded-bottom mb-2"
          onClick={handlePublishTask}
        >
          <FontAwesomeIcon className="mr-2" icon="share" />
          Publish
        </button>
      )}
    </>
  );
}

function BuilderActions({
  validExamples,
  clearSuggests,
}) {
  const dispatch = useDispatch();

  const { taskService } = useContext(RoomContext);

  const taskMachineState = useMachineStateSelector(taskService, taskStateSelector);
  const isIdleTaskState = isIdleStateTaskSelector(taskMachineState);
  const isSavedTask = isSavedTaskSelector(taskMachineState);
  const isInvalidTaskMachineState = isInvalidTaskSelector(taskMachineState);

  const isSavingPrepare = isTaskPrepareSavingSelector(taskMachineState);
  const isTestingPrepare = isTaskPrepareTestingSelector(taskMachineState);

  const isAdmin = useSelector(selectors.currentUserIsAdminSelector);
  const isOwner = useSelector(selectors.isTaskOwner);

  const task = useSelector(
    state => state.builder.task,
  );

  const notPublished = task.state === taskStateCodes.draft
    && task.state === taskStateCodes.moderation;
  const readyTesting = validExamples;
  const readySave = useSelector(selectors.isValidTask);

  const disabledTestingBtn = (
    task.state === taskStateCodes.active || isSavedTask
      ? false
      : !readyTesting || isInvalidTaskMachineState || isTestingPrepare
  );
  const disabledSaveBtn = !readySave || isInvalidTaskMachineState || isSavingPrepare || isSavedTask;

  const buildAsserts = useCallback(
    () => dispatch(buildTaskAsserts(taskService)),
    [taskService, dispatch],
  );
  const finishPreparing = useCallback(
    () => taskService.send('SUCCESS'),
    [taskService],
  );
  const prepareAsserts = isIdleTaskState ? buildAsserts : finishPreparing;

  const handleOpenTesting = useCallback(() => {
    clearSuggests();

    taskService.send('START_TESTING');
    prepareAsserts();
  }, [taskService, prepareAsserts, clearSuggests]);

  const handleSaveTask = useCallback(() => {
    clearSuggests();

    taskService.send('START_SAVING');
    prepareAsserts();
  }, [taskService, prepareAsserts, clearSuggests]);

  const handleDeleteTask = useCallback(() => {
    // eslint-disable-next-line no-alert
    if (window.confirm('Are you sure you want to delete this task?')) {
      dispatch(deleteTask(task.id));
    }
  }, [task.id, dispatch]);
  const handlePublishTask = useCallback(() => {
    dispatch(publishTask(task.id));
  }, [task.id, dispatch]);
  const handleUnpublishTask = useCallback(() => {
    dispatch(updateTaskState(task.id, taskStateCodes.draft));
  }, [task.id, dispatch]);
  const handleActivateTask = useCallback(() => {
    dispatch(updateTaskState(task.id, taskStateCodes.active));
  }, [task.id, dispatch]);
  const handleDisableTask = useCallback(() => {
    dispatch(updateTaskState(task.id, taskStateCodes.disabled));
  }, [task.id, dispatch]);

  const openUploadTaskModal = useCallback(() => {
    NiceModal.show(ModalCodes.taskParamsModal, { mode: modalModes.editJSON, action: modalActions.upload });
  }, []);
  const openCopyTaskModal = useCallback(() => {
    NiceModal.show(ModalCodes.taskParamsModal, { mode: modalModes.showJSON, action: modalActions.copy });
  }, []);
  const openEditTaskModal = useCallback(() => {
    NiceModal.show(ModalCodes.taskParamsModal, { mode: modalModes.editJSON, action: modalActions.edit });
  }, []);

  if (!(isAdmin || isOwner)) {
    return null;
  }

  return (
    <>
      {task.state === taskStateCodes.blank && (
        <button
          type="button"
          className="btn btn-md btn-secondary text-nowrap rounded-lg mb-2"
          onClick={openUploadTaskModal}
        >
          <FontAwesomeIcon className="mr-2" icon="upload" />
          Upload
        </button>
      )}
      {task.state !== taskStateCodes.blank && (
        <>
          <button
            type="button"
            className="btn btn-md btn-secondary text-nowrap rounded-lg mb-2"
            onClick={openCopyTaskModal}
          >
            <FontAwesomeIcon className="mr-2" icon="copy" />
            Copy
          </button>
          <button
            type="button"
            className="btn btn-md btn-secondary text-nowrap rounded-lg mb-2"
            onClick={openEditTaskModal}
          >
            <FontAwesomeIcon className="mr-2" icon="edit" />
            Edit
          </button>
        </>
      )}
      <button
        type="button"
        className="btn btn-md btn-secondary text-nowrap rounded-lg mb-2"
        onClick={handleOpenTesting}
        disabled={disabledTestingBtn}
      >
        {isTestingPrepare ? (
          <span
            className="spinner-border spinner-border-sm mr-2"
            role="status"
            aria-hidden="true"
          />
        ) : (
          <FontAwesomeIcon className="mr-2" icon="play" />
        )}
        <span>Testing</span>
      </button>
      <button
        type="button"
        className="btn btn-md btn-success text-nowrap text-white rounded-lg mb-2"
        onClick={handleSaveTask}
        disabled={disabledSaveBtn}
      >
        {isSavingPrepare ? (
          <span
            className="spinner-border spinner-border-sm mr-2"
            role="status"
            aria-hidden="true"
          />
        ) : (
          <FontAwesomeIcon className="mr-2" icon="save" />
        )}
        <span>{isSavedTask ? 'Saved' : 'Save'}</span>
      </button>
      {notPublished && (
        <button
          type="button"
          className="btn btn-md btn-danger text-nowrap rounded-lg mb-2"
          onClick={handleDeleteTask}
        >
          <FontAwesomeIcon className="mr-2" icon="trash" />
          Delete
        </button>
      )}
      {task.state === taskStateCodes.draft && (
        <button
          type="button"
          className="btn btn-md btn-primary text-nowrap rounded-lg mb-2"
          onClick={handlePublishTask}
        >
          <FontAwesomeIcon className="mr-2" icon="paper-plane" />
          Publish
        </button>
      )}
      {task.state === taskStateCodes.moderation && (
        <ModerationActions
          canPublish={isAdmin}
          canUnpublish={isOwner || isAdmin}
          handlePublishTask={handlePublishTask}
          handleUnpublishTask={handleUnpublishTask}
        />
      )}
      {task.state === taskStateCodes.active && (
        <button
          type="button"
          className="btn btn-md btn-danger text-nowrap rounded-lg mb-2"
          onClick={handleDisableTask}
        >
          <FontAwesomeIcon className="mr-2" icon="times-circle" />
          Disable
        </button>
      )}
      {task.state === taskStateCodes.disabled && (
        <button
          type="button"
          className="btn btn-md btn-primary text-nowrap rounded-lg mb-2"
          onClick={handleActivateTask}
        >
          <FontAwesomeIcon className="mr-2" icon="check-circle" />
          Activate
        </button>
      )}
    </>
  );
}

export default BuilderActions;