crane-cloud/frontend

View on GitHub
src/pages/UserProjectsPage/index.jsx

Summary

Maintainability
B
4 hrs
Test Coverage
F
37%
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Modal from "../../components/Modal";
import styles from "./UserProjectsPage.module.css";
import InformationBar from "../../components/InformationBar";
import { ReactComponent as ButtonPlus } from "../../assets/images/buttonplus.svg";
import Header from "../../components/Header";
import Pagination from "../../components/Pagination";
import getClustersList from "../../redux/actions/clusters";
import CreateProject from "../../components/CreateProject";
import getUserProjects from "../../redux/actions/projectsList";
import getUserCredits from "../../redux/actions/userCredits";
import ProjectCard from "../../components/ProjectCard";
import PrimaryButton from "../../components/PrimaryButton";
import Spinner from "../../components/Spinner";
import { handlePatchRequest } from "../../apis/apis.js";
import "../../index.css";

class UserProjectsPage extends React.Component {
  constructor(props) {
    super(props);
    this.initialState = {
      openCreateComponent: false,
      Searchword: "",
      // SearchList: [],
      myProjectsList: [],
      sharedProjectsList: [],
      showInviteModel: false,
      selectedProjects: "My projects",
      inviteeProjectId: "",
      inviteeModelRole: "",
      decliningInvitation: false,
      acceptingInvitation: false,
      invitationError: "",
      currentTab: "My Projects",
      currentPaginationPage: 1,
      displayProjects: [],
    };
    this.state = this.initialState;
    this.openProjectCreateComponent =
      this.openProjectCreateComponent.bind(this);
    this.callbackProjectCreateComponent =
      this.callbackProjectCreateComponent.bind(this);
    this.searchThroughProjects = this.searchThroughProjects.bind(this);
    this.handleCallbackSearchword = this.handleCallbackSearchword.bind(this);
    this.showInvitationModel = this.showInvitationModel.bind(this);
    this.hideInvitationModel = this.hideInvitationModel.bind(this);
    this.handleInvitationAcceptence =
      this.handleInvitationAcceptence.bind(this);
    this.handleInvitationDecline = this.handleInvitationDecline.bind(this);
    this.filterProjects = this.filterProjects.bind(this);
    this.onPageChange = this.onPageChange.bind(this);
    this.handleTabChange = this.handleTabChange.bind(this);
    this.handleSharedProjectsTabChange =
      this.handleSharedProjectsTabChange.bind(this);
    this.handleTabAll = this.handleTabAll.bind(this);
    this.onFilterSelect = this.onFilterSelect.bind(this);
  }
  handleSharedProjectsTabChange() {
    this.setState({
      selectedProjects: "Shared Projects",
      currentTab: "Shared Projects",
    });
  }
  handleTabChange() {
    this.setState({
      selectedProjects: "My projects",
      currentTab: "My Projects",
    });
  }


  handleTabAll(selectedOption) {
    const { myProjectsList, sharedProjectsList } = this.state;
    let displayProjects;
    if (selectedOption === "All") {
      displayProjects = myProjectsList.concat(sharedProjectsList);
    } else if (selectedOption === "My Projects") {
      displayProjects = myProjectsList;
    } else if (selectedOption === "Shared Projects") {
      displayProjects = sharedProjectsList;
    } else {
      displayProjects = myProjectsList.concat(sharedProjectsList);
    }
    this.setState({
      selectedProjects: selectedOption,
      currentTab: selectedOption,
      displayProjects: displayProjects,
    });
  }
  componentDidMount() {
    const { getClustersList, getUserProjects, data, getUserCredits } =
      this.props;
    //add page 1
    getUserProjects(this.state.currentPaginationPage);
    getClustersList();
    getUserCredits(data.id);
  }
  componentDidUpdate(prevProps, prevState) {
    const {
      isAdded,
      getClustersList,
      getUserProjects,
      isDeleted,
      isUpdated,
      clearUpdateProjectState,
      isFetched,
    } = this.props;
    const { Searchword } = this.state;

    if (isDeleted !== prevProps.isDeleted) {
      getUserProjects(this.state.currentPaginationPage);
      getClustersList();
    }

    if (isUpdated !== prevProps.isUpdated) {
      clearUpdateProjectState();
      getUserProjects(this.state.currentPaginationPage);
      getClustersList();
    }

    if (isAdded !== prevProps.isAdded) {
      getUserProjects(this.state.currentPaginationPage);
      this.setState(this.initialState);
    }
    if (isFetched !== prevProps.isFetched) {
      //call filter
      const filteredData = this.filterProjects();
      this.setState({
        myProjectsList: filteredData.myProjects,
        sharedProjectsList: filteredData.sharedProjects,
      });
    }
    if (Searchword !== prevState.Searchword) {
      this.searchThroughProjects();
    }

    if (isFetched && !prevProps.isFetched) {
      const { myProjectsList, sharedProjectsList } = this.state;
      const displayProjects = myProjectsList.concat(sharedProjectsList);
      this.setState({
        selectedProjects: "All",
        currentTab: "All",
        displayProjects: displayProjects,
      });
    }
  }

  openProjectCreateComponent() {
    this.setState({ openCreateComponent: true });
  }
  callbackProjectCreateComponent() {
    const { getUserProjects } = this.props;
    this.setState(this.initialState);
    getUserProjects(this.state.currentPaginationPage);
  }
  searchThroughProjects() {
    const { Searchword } = this.state;
    const { getUserProjects } = this.props;
    //reset pagination
    this.setState({ currentPaginationPage: 1 });
    getUserProjects(1, Searchword);
    // ?keywords=black
    // let searchResult = [];
    // projects.forEach((element) => {
    //   if (element.name.toLowerCase().includes(Searchword.toLowerCase())) {
    //     searchResult.push(element);
    //   }
    // });
    // this.setState({
    //   SearchList: searchResult.sort((a, b) =>
    //     b.date_created > a.date_created ? 1 : -1
    //   ),
    // });
  }
  hideInvitationModel() {
    this.setState({ showInviteModel: false });
  }
  showInvitationModel(inviteeProjectId, inviteeModelRole) {
    this.setState({
      showInviteModel: true,
      inviteeProjectId: inviteeProjectId,
      inviteeModelRole: inviteeModelRole,
    });
  }
  handleCallbackSearchword(word) {
    this.setState({
      Searchword: word,
    });
  }
  handleInvitationDecline() {
    const { getUserProjects } = this.props;
    this.setState({
      decliningInvitation: true,
    });
    const { inviteeProjectId } = this.state;
    handlePatchRequest(`/projects/${inviteeProjectId}/users/handle_invite`, {
      accepted_collaboration_invite: false,
    })
      .then((response) => {
        this.setState({
          decliningInvitation: false,
        });
        this.hideInvitationModel();
        getUserProjects(this.state.currentPaginationPage);
      })
      .catch((error) => {
        this.setState({
          invitationError: "Something went wrong",
          decliningInvitation: false,
        });
        this.hideInvitationModel();
      });
  }
  handleInvitationAcceptence() {
    const { getUserProjects } = this.props;
    this.setState({
      acceptingInvitation: true,
    });
    const { inviteeProjectId } = this.state;

    handlePatchRequest(`/projects/${inviteeProjectId}/users/handle_invite`, {
      accepted_collaboration_invite: true,
    })
      .then((response) => {
        this.setState({
          acceptingInvitation: false,
        });
        this.hideInvitationModel();
        getUserProjects(this.state.currentPaginationPage);
      })
      .catch((error) => {
        this.setState({
          invitationError: "Something went wrong",
          acceptingInvitation: false,
        });
        this.hideInvitationModel();
      });
  }
  filterProjects() {
    const { projects, data } = this.props;
    let myProjects = [];
    let sharedProjects = [];
    projects.forEach((element) => {
      if (element.owner_id === data.id) {
        myProjects.push(element);
      } else {
        sharedProjects.push(element);
      }
    });
    //by default show category with more  projects
    if (myProjects.length < sharedProjects.length) {
      this.setState({
        selectedProjects: "Shared Projects",
        currentTab: "Shared Projects",
      });
    } else {
      this.setState({
        selectedProjects: "My projects",
        currentTab: "My projects",
      });
    }
    return {
      sharedProjects,
      myProjects,
    };
  }
  onPageChange(page) {
    const { getUserProjects } = this.props;
    this.setState({
      currentPaginationPage: page,
    });
    getUserProjects(page);
  }
  /*onFilterSelect(selectedOption, displayProjects) {
    this.setState({
      selectedProjects: selectedOption,
      displayProjects: displayProjects,
    });
  }*/
  onFilterSelect(selectedOption) {
    this.setState((prevState) => ({
      selectedProjects: selectedOption,
      displayProjects:
        selectedOption === "All"
          ? prevState.myProjectsList.concat(prevState.sharedProjectsList)
          : selectedOption === "Shared Projects"
          ? prevState.sharedProjectsList
          : prevState.myProjectsList,
    }));
  }

  render() {
    const {
      openCreateComponent,
      Searchword,
      // SearchList,
      showInviteModel,
      selectedProjects,
      inviteeModelRole,
      decliningInvitation,
      acceptingInvitation,
      invitationError,
      sharedProjectsList,
      myProjectsList,
    } = this.state;
    const {
      projects,
      pagination,
      isRetrieving,
      isFetched,
      match: { params },
      credits,
      data,
    } = this.props;
    const displayProjects =
      selectedProjects === "All"
        ? myProjectsList.concat(sharedProjectsList)
        : selectedProjects === "Shared Projects"
        ? sharedProjectsList
        : myProjectsList;

    const sortedProjects = displayProjects.sort((a, b) =>
      b.date_created > a.date_created ? 1 : -1
    );
    const adminCheck = data.username === "admin";
    return (
      <div className={styles.Page}>
        {openCreateComponent ? (
          <CreateProject
            closeComponent={this.callbackProjectCreateComponent}
            params={params}
          />
        ) : (
          <div>
            <div className={styles.TopRow}>
              <Header credits={credits?.amount} />
              <InformationBar
                header="Projects"
                showBtn
                buttontext="+ New Project"
                showSearchBar
                selectedProjects={selectedProjects}
                myProjectsList={myProjectsList}
                sharedProjectsList={sharedProjectsList}
                viewFilter={true}
                placeholder="Search through projects"
                btnAction={this.openProjectCreateComponent}
                searchAction={this.handleCallbackSearchword}
                adminRoute={adminCheck}
                onFilterSelect={this.onFilterSelect}
              />
            </div>
            <div className={styles.MainRow}>
              <div className={`${styles.SelectProjects} SmallContainer`}></div>
              {isRetrieving ? (
                <div className={styles.NoResourcesMessage}>
                  <div className={styles.SpinnerWrapper}>
                    <Spinner size="big" />
                  </div>
                </div>
              ) : Searchword !== "" ? (
                <div className={`${styles.ProjectList}  SmallContainer`}>
                  {isFetched &&
                    sortedProjects !== undefined &&
                    sortedProjects.map((project) => (
                      <ProjectCard
                        key={project.id}
                        name={project.name}
                        description={project.description}
                        cardID={project.id}
                        acceptInviteCallBackModel={this.showInvitationModel}
                        userID={data.id}
                        ownerId={project.owner_id}
                        apps_count={project.apps_count}
                        disabled={project.disabled}
                        admin_disabled={project.admin_disabled}
                      />
                    ))}
                </div>
              ) : (
                <div className={`${styles.ProjectList}  SmallContainer`}>
                  {isFetched &&
                    sortedProjects !== undefined &&
                    sortedProjects.map((project) => (
                      <ProjectCard
                        key={project.id}
                        name={project.name}
                        description={project.description}
                        cardID={project.id}
                        userID={data.id}
                        ownerId={project.owner_id}
                        acceptInviteCallBackModel={this.showInvitationModel}
                        apps_count={project.apps_count}
                        disabled={project.disabled}
                        admin_disabled={project.admin_disabled}
                      />
                    ))}
                </div>
              )}

              {showInviteModel === true && (
                <div className={styles.ProjectDeleteModel}>
                  <Modal
                    showModal={showInviteModel}
                    onClickAway={this.hideInvitationModel}
                  >
                    <div className={styles.ModelContent}>
                      <div className={styles.ModelHeader}>
                        Invitation to this project{" "}
                      </div>
                      <div className={styles.UpdateForm}>
                        <div className={styles.InformationText}>
                          You have been invited to join this project as (a/an)
                          <span className={styles.Role}>
                            {" "}
                            {inviteeModelRole}.
                          </span>
                        </div>
                        <div className={styles.InformationWarning}>
                          If you decline, you will not be able to see this
                          project again unless you are re-invited.
                        </div>
                        <div className={styles.UpdateProjectModelButtons}>
                          <PrimaryButton
                            color="red"
                            onClick={() => {
                              this.handleInvitationDecline();
                            }}
                          >
                            {decliningInvitation ? <Spinner /> : "Decline"}
                          </PrimaryButton>
                          <PrimaryButton
                            color="primary"
                            onClick={() => {
                              this.handleInvitationAcceptence();
                            }}
                          >
                            {acceptingInvitation ? <Spinner /> : "Accept"}
                          </PrimaryButton>
                        </div>
                        {invitationError && (
                          <div className={styles.InformationWarning}>
                            {invitationError}
                          </div>
                        )}
                      </div>
                    </div>
                  </Modal>
                </div>
              )}
              {displayProjects.length === 0 && isFetched && (
                <div className={styles.NoResourcesMessage}>
                  {selectedProjects === "My projects" ? (
                    <>
                      {this.state.currentPaginationPage === 1 ? (
                        <div>
                          You haven’t created any projects yet. Click the &nbsp;{" "}
                          <ButtonPlus className={styles.ButtonPlusSmall} />{" "}
                          &nbsp; button to add a project.
                        </div>
                      ) : (
                        <div>
                          This page of you personal projects contains no
                          projects, switch tabs to shared to see Projects &nbsp;
                          Or click{" "}
                          <ButtonPlus className={styles.ButtonPlusSmall} />
                          &nbsp; button to add a project.
                        </div>
                      )}
                    </>
                  ) : (
                    <>
                      {this.state.currentPaginationPage === 1 ? (
                        <>No project has been shared with you as yet.</>
                      ) : (
                        <>This page contains no shared projects.</>
                      )}
                    </>
                  )}
                </div>
              )}
              {!isFetched && !projects.length === 0 && (
                <div className={styles.NoResourcesMessage}>
                  You haven’t created any projects yet. Click the &nbsp;{" "}
                  <ButtonPlus className={styles.ButtonPlusSmall} /> &nbsp;
                  button to add a project.
                </div>
              )}
              {!isRetrieving && !isFetched && (
                <div className={styles.NoResourcesMessage}>
                  Oops! Something went wrong! Failed to retrieve Projects.
                </div>
              )}
            </div>
          </div>
        )}
        <div className={styles.FooterRow}>
          {pagination?.pages > 1 &&
            !isRetrieving &&
            isFetched &&
            !openCreateComponent && (
              <div className={styles.PaginationSection}>
                {/* customise pagination for shared and personal projects */}
                <Pagination
                  total={pagination?.pages}
                  current={this.state.currentPaginationPage}
                  onPageChange={this.onPageChange}
                />
              </div>
            )}
          <div>
            Copyright {new Date().getFullYear()} Crane Cloud. All Rights
            Reserved.
          </div>
        </div>
      </div>
    );
  }
}

UserProjectsPage.propTypes = {
  projects: PropTypes.arrayOf(PropTypes.shape({})),
  clusters: PropTypes.object,
  pagination: PropTypes.shape({}),
  getClustersList: PropTypes.func.isRequired,
  getUserProjects: PropTypes.func.isRequired,
  data: PropTypes.shape({
    id: PropTypes.string,
  }).isRequired,
  match: PropTypes.shape({
    params: PropTypes.shape({
      userID: PropTypes.string,
    }).isRequired,
  }).isRequired,
  isFetched: PropTypes.bool,
  isRetrieving: PropTypes.bool,
};

UserProjectsPage.defaultProps = {
  clusters: [],
  projects: [],
  pagination: {},
  isFetched: false,
  isRetrieving: false,
};

export const mapStateToProps = (state) => {
  const { data } = state.user;

  const { clusters } = state.clustersReducer;
  const { isRetrieving, projects, isFetched, pagination } =
    state.userProjectsReducer;
  const { credits } = state.userCreditsReducer;
  return {
    data,
    isRetrieving,
    projects,
    pagination,
    clusters,
    isFetched,
    credits,
  };
};

const mapDispatchToProps = {
  getUserProjects,
  getClustersList,
  getUserCredits,
};

export default connect(mapStateToProps, mapDispatchToProps)(UserProjectsPage);