From 908d1e37c997214a51cda0126608835da2f29d90 Mon Sep 17 00:00:00 2001
From: PintoGideon <gideonpinto123@gmail.com>
Date: Thu, 15 Feb 2024 14:51:00 -0500
Subject: [PATCH 1/3] Remove the delete option and only allow it if the
 is_staff property on the user payload is true

---
 src/api/common.ts                         |  25 ++-
 src/components/CreateFeed/CreateFeed.tsx  |   2 +-
 src/components/CreateFeed/Pipelines.tsx   | 241 +++++++++++-----------
 src/components/Feeds/FeedListView.tsx     |  47 ++---
 src/components/Feeds/FeedView.tsx         |  32 +--
 src/components/Feeds/usePaginate.tsx      |   9 +-
 src/components/Feeds/utilties.ts          |   2 -
 src/components/LibraryCopy/FolderCard.tsx |  47 +++--
 src/components/Login/index.tsx            |  50 +++--
 src/components/Signup/SignUpForm.tsx      |  60 +++---
 src/store/feed/reducer.ts                 |  17 +-
 src/store/user/actions.ts                 |   1 +
 src/store/user/reducer.ts                 |   2 +
 src/store/user/types.ts                   |   1 +
 14 files changed, 291 insertions(+), 245 deletions(-)

diff --git a/src/api/common.ts b/src/api/common.ts
index 3d7511ff3..0c7d4e17f 100644
--- a/src/api/common.ts
+++ b/src/api/common.ts
@@ -1,7 +1,13 @@
 import * as React from "react";
 import axios, { AxiosProgressEvent } from "axios";
 import ChrisAPIClient from "./chrisapiclient";
-import { Pipeline, PipelineList, PluginPiping, Feed } from "@fnndsc/chrisapi";
+import {
+  Pipeline,
+  type PipelineList,
+  PluginPiping,
+  Feed,
+  PipelineInstance,
+} from "@fnndsc/chrisapi";
 
 export function useSafeDispatch(dispatch: any) {
   const mounted = React.useRef(false);
@@ -184,7 +190,8 @@ export const fetchPipelines = async (
   } = {
     error_message: "",
   };
-  let registeredPipelinesList, registeredPipelines;
+  let registeredPipelinesList: PipelineList;
+  let registeredPipelines: PipelineInstance[];
   const offset = perPage * (page - 1);
   const client = ChrisAPIClient.getClient();
   const params = {
@@ -194,17 +201,17 @@ export const fetchPipelines = async (
   };
   try {
     registeredPipelinesList = await client.getPipelines(params);
-    registeredPipelines = registeredPipelinesList.getItems();
+    registeredPipelines =
+      (registeredPipelinesList.getItems() as PipelineInstance[]) || [];
+    return {
+      registeredPipelines,
+      registeredPipelinesList,
+      error: errorPayload,
+    };
   } catch (error) {
     const errorObj = catchError(error);
     errorPayload = errorObj;
   }
-
-  return {
-    registeredPipelines,
-    registeredPipelinesList,
-    error: errorPayload,
-  };
 };
 
 export async function fetchResources(pipelineInstance: Pipeline) {
diff --git a/src/components/CreateFeed/CreateFeed.tsx b/src/components/CreateFeed/CreateFeed.tsx
index 740ad1d3b..e891e6b9e 100644
--- a/src/components/CreateFeed/CreateFeed.tsx
+++ b/src/components/CreateFeed/CreateFeed.tsx
@@ -251,7 +251,7 @@ export default function CreateFeed() {
             )}
           </WizardStep>
           <WizardStep id={3} name="Pipelines">
-            <PipelineContainer />
+            <PipelineContainer justDisplay={true} />
           </WizardStep>
           <WizardStep
             id={4}
diff --git a/src/components/CreateFeed/Pipelines.tsx b/src/components/CreateFeed/Pipelines.tsx
index b0d7cc4a9..d86407130 100644
--- a/src/components/CreateFeed/Pipelines.tsx
+++ b/src/components/CreateFeed/Pipelines.tsx
@@ -20,6 +20,7 @@ import {
 import { useQuery } from "@tanstack/react-query";
 import { ErrorAlert } from "../Common";
 import type { Pipeline } from "@fnndsc/chrisapi";
+import { EmptyStateComponent } from "../Common";
 import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon";
 
 import {
@@ -37,6 +38,7 @@ import {
 } from "../../api/common";
 import type { PipelinesProps } from "./types/pipeline";
 import { Alert } from "antd";
+import { useTypedSelector } from "../../store/hooks";
 
 export const PIPELINEQueryTypes = {
   NAME: ["Name", "Match plugin name containing this string"],
@@ -75,6 +77,7 @@ const Pipelines = ({
   handleSetCurrentComputeEnv,
   handleFormParameters,
 }: PipelinesProps) => {
+  const showDelete = useTypedSelector((state) => state.user.isStaff);
   const { pipelineData, selectedPipeline, pipelines } = state;
   const [errors, setErrors] = React.useState({});
   const { goToNextStep: onNext, goToPrevStep: onBack } =
@@ -119,23 +122,21 @@ const Pipelines = ({
         dropdownValue.toLowerCase(),
       );
 
-      const { registeredPipelines, registeredPipelinesList, error } = data;
-
-      if (registeredPipelines) {
-        handleDispatchWrap(registeredPipelines);
+      if (data?.registeredPipelines) {
+        handleDispatchWrap(data?.registeredPipelines);
       }
 
-      if (registeredPipelinesList) {
+      if (data?.registeredPipelinesList) {
         setPageState((pageState) => {
           return {
             ...pageState,
-            itemCount: registeredPipelinesList.totalCount,
+            itemCount: data?.registeredPipelinesList.totalCount,
           };
         });
       }
 
-      if (error.error_message) {
-        throw new Error(error.error_message);
+      if (data?.error.error_message) {
+        throw new Error(data?.error.error_message);
       }
       return data;
     },
@@ -188,7 +189,7 @@ const Pipelines = ({
 
     if (el) {
       //@ts-ignore
-      el!.scrollIntoView({ block: "center", behavior: "smooth" });
+      el?.scrollIntoView({ block: "center", behavior: "smooth" });
     }
   });
 
@@ -238,7 +239,7 @@ const Pipelines = ({
       }
       //Not already expanded or not previous fetched and cached in state
       if (
-        !(expanded && expanded[pipeline.data.id]) ||
+        !expanded?.[pipeline.data.id] ||
         !state.pipelineData[pipeline.data.id]
       ) {
         setPipeline(pipeline);
@@ -265,7 +266,10 @@ const Pipelines = ({
 
   const handleKeyDown = useCallback(
     (e: any, pipeline: Pipeline) => {
-      if (e.code == "Enter" && e.target.closest("DIV.pf-c-data-list__toggle")) {
+      if (
+        e.code === "Enter" &&
+        e.target.closest("DIV.pf-c-data-list__toggle")
+      ) {
         e.preventDefaut();
         handleOnExpand(pipeline);
       }
@@ -275,9 +279,9 @@ const Pipelines = ({
 
   const handleBrowserKeyDown = useCallback(
     (e: any) => {
-      if (e.code == "ArrowLeft") {
+      if (e.code === "ArrowLeft") {
         onBack();
-      } else if (e.code == "ArrowRight") {
+      } else if (e.code === "ArrowRight") {
         onNext();
       }
     },
@@ -356,7 +360,7 @@ const Pipelines = ({
             customIcon={<SearchIcon />}
             aria-label="search"
             onChange={(_event, value: string) => {
-              handlePipelineSearch && handlePipelineSearch(value.toLowerCase());
+              handlePipelineSearch?.(value.toLowerCase());
             }}
           />
         </div>
@@ -368,69 +372,64 @@ const Pipelines = ({
           onPerPageSelect={onPerPageSelect}
         />
       </div>
-
       <DataList aria-label="pipeline list">
         {isError && (
           <Alert type="error" description={<div>{error.message}</div>} />
         )}
-
         {isLoading ? (
           <SpinContainer title="Fetching Pipelines" />
-        ) : (
-          pipelines.length > 0 &&
-          pipelines.map((pipeline: any) => {
-            return (
-              <DataListItem
-                isExpanded={
-                  expanded && expanded[pipeline.data.id] ? true : false
-                }
-                key={pipeline.data.id}
-              >
-                <DataListItemRow>
-                  <DataListToggle
-                    id={pipeline.data.id}
-                    aria-controls="expand"
-                    onKeyDown={(e) => handleKeyDown(e, pipeline)}
-                    onClick={() => handleOnExpand(pipeline)}
-                  />
-                  <DataListItemCells
-                    dataListCells={[
-                      <DataListCell key={pipeline.data.name}>
-                        <div
-                          className="plugin-table-row"
-                          key={pipeline.data.name}
-                        >
-                          <span className="plugin-table-row__plugin-name">
-                            {pipeline.data.name}
-                          </span>
-                          <span
-                            className="plugin-table-row__plugin-description"
-                            id={`${pipeline.data.description}`}
-                          >
-                            <em>{pipeline.data.description}</em>
-                          </span>
-                        </div>
-                      </DataListCell>,
-                    ]}
-                  />
-                  <DataListAction
-                    aria-labelledby="select a pipeline"
-                    id={pipeline.data.id}
-                    aria-label="actions"
-                    className="pipelines"
-                  >
-                    {!justDisplay && (
-                      <Button
-                        variant="primary"
-                        key="select-action"
-                        onClick={() => handleOnButtonClick(pipeline)}
+        ) : pipelines.length > 0 ? (
+          pipelines.map((pipeline: any) => (
+            <DataListItem
+              isExpanded={expanded?.[pipeline.data.id] ? true : false}
+              key={pipeline.data.id}
+            >
+              <DataListItemRow>
+                <DataListToggle
+                  id={pipeline.data.id}
+                  aria-controls="expand"
+                  onKeyDown={(e) => handleKeyDown(e, pipeline)}
+                  onClick={() => handleOnExpand(pipeline)}
+                />
+                <DataListItemCells
+                  dataListCells={[
+                    <DataListCell key={pipeline.data.name}>
+                      <div
+                        className="plugin-table-row"
+                        key={pipeline.data.name}
                       >
-                        {selectedPipeline === pipeline.data.id
-                          ? "Deselect"
-                          : "Select"}
-                      </Button>
-                    )}
-
+                        <span className="plugin-table-row__plugin-name">
+                          {pipeline.data.name}
+                        </span>
+                        <span
+                          className="plugin-table-row__plugin-description"
+                          id={`${pipeline.data.description}`}
+                        >
+                          <em>{pipeline.data.description}</em>
+                        </span>
+                      </div>
+                    </DataListCell>,
+                  ]}
+                />
+                <DataListAction
+                  aria-labelledby="select a pipeline"
+                  id={pipeline.data.id}
+                  aria-label="actions"
+                  className="pipelines"
+                >
+                  {!justDisplay && (
+                    <Button
+                      variant="primary"
+                      key="select-action"
+                      onClick={() => handleOnButtonClick(pipeline)}
+                    >
+                      {selectedPipeline === pipeline.data.id
+                        ? "Deselect"
+                        : "Select"}
+                    </Button>
+                  )}
+                  {/*Hardcode to only allow the user 'chris' to delete the pipelines*/}
+                  {showDelete && (
                     <Button
                       key="delete-action"
                       onClick={async () => {
@@ -453,61 +452,65 @@ const Pipelines = ({
                     >
                       Delete
                     </Button>
-                  </DataListAction>
-                </DataListItemRow>
-                <DataListContent
-                  id={pipeline.data.id}
-                  aria-label="PrimaryContent"
-                  isHidden={!(expanded && expanded[pipeline.data.id])}
-                >
-                  {((expanded && expanded[pipeline.data.id]) ||
-                    state.pipelineData[pipeline.data.id]) &&
-                  !isResourcesLoading &&
-                  !isResourceError ? (
-                    <>
-                      <div
-                        style={{
-                          display: "flex",
-                          justifyContent: "space-between",
-                        }}
-                      >
-                        <Tree
-                          state={state.pipelineData[pipeline.data.id]}
-                          currentPipelineId={pipeline.data.id}
-                          handleNodeClick={handleNodeClick}
-                          handleSetCurrentNode={handleSetCurrentNode}
-                          handleSetPipelineEnvironments={
-                            handleSetPipelineEnvironments
-                          }
-                          handleSetCurrentNodeTitle={handleSetCurrentNodeTitle}
-                        />
-                        <GeneralCompute
-                          handleSetGeneralCompute={handleSetGeneralCompute}
-                          currentPipelineId={pipeline.data.id}
-                        />
-                      </div>
+                  )}
+                </DataListAction>
+              </DataListItemRow>
 
-                      <ConfigurationPage
-                        justDisplay={justDisplay}
-                        pipelines={pipelines}
-                        pipeline={pipeline}
-                        currentPipelineId={pipeline.data.id}
+              <DataListContent
+                id={pipeline.data.id}
+                aria-label="PrimaryContent"
+                isHidden={!expanded?.[pipeline.data.id]}
+              >
+                {(expanded?.[pipeline.data.id] ||
+                  state.pipelineData[pipeline.data.id]) &&
+                !isResourcesLoading &&
+                !isResourceError ? (
+                  <>
+                    <div
+                      style={{
+                        display: "flex",
+                        justifyContent: "space-between",
+                      }}
+                    >
+                      <Tree
                         state={state.pipelineData[pipeline.data.id]}
+                        currentPipelineId={pipeline.data.id}
+                        handleNodeClick={handleNodeClick}
+                        handleSetCurrentNode={handleSetCurrentNode}
+                        handleSetPipelineEnvironments={
+                          handleSetPipelineEnvironments
+                        }
                         handleSetCurrentNodeTitle={handleSetCurrentNodeTitle}
-                        handleDispatchPipelines={handleDispatchPipelines}
-                        handleSetCurrentComputeEnv={handleSetCurrentComputeEnv}
-                        handleFormParameters={handleFormParameters}
                       />
-                    </>
-                  ) : (
-                    <SpinContainer title="Fetching Pipeline Resources" />
-                  )}
-                </DataListContent>
-              </DataListItem>
-            );
-          })
+                      <GeneralCompute
+                        handleSetGeneralCompute={handleSetGeneralCompute}
+                        currentPipelineId={pipeline.data.id}
+                      />
+                    </div>
+
+                    <ConfigurationPage
+                      justDisplay={justDisplay}
+                      pipelines={pipelines}
+                      pipeline={pipeline}
+                      currentPipelineId={pipeline.data.id}
+                      state={state.pipelineData[pipeline.data.id]}
+                      handleSetCurrentNodeTitle={handleSetCurrentNodeTitle}
+                      handleDispatchPipelines={handleDispatchPipelines}
+                      handleSetCurrentComputeEnv={handleSetCurrentComputeEnv}
+                      handleFormParameters={handleFormParameters}
+                    />
+                  </>
+                ) : (
+                  <SpinContainer title="Fetching Pipeline Resources" />
+                )}
+              </DataListContent>
+            </DataListItem>
+          ))
+        ) : (
+          <EmptyStateComponent title="No Pipelines were found registered to your ChRIS instance" />
         )}
       </DataList>
+
       <div id="error">
         {Object.keys(errors).length > 0 && (
           <ErrorAlert errors={errors} cleanUpErrors={() => setErrors({})} />
diff --git a/src/components/Feeds/FeedListView.tsx b/src/components/Feeds/FeedListView.tsx
index f2bc8f351..a2d2880f3 100644
--- a/src/components/Feeds/FeedListView.tsx
+++ b/src/components/Feeds/FeedListView.tsx
@@ -1,5 +1,5 @@
-import React, { useContext, useMemo } from "react";
-import { useLocation, useNavigate } from "react-router";
+import React, { useContext } from "react";
+import { useNavigate } from "react-router";
 import { useDispatch } from "react-redux";
 import { format } from "date-fns";
 import { useQuery } from "@tanstack/react-query";
@@ -25,7 +25,7 @@ import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon";
 import { Typography } from "antd";
 import { cujs } from "chris-utility";
 import { useTypedSelector } from "../../store/hooks";
-import { usePaginate } from "./usePaginate";
+import { usePaginate, useSearchQueryParams } from "./usePaginate";
 import {
   setBulkSelect,
   removeBulkSelect,
@@ -46,12 +46,6 @@ import { fetchPublicFeeds, fetchFeeds } from "./utilties";
 
 const { Paragraph } = Typography;
 
-function useSearchQueryParams() {
-  const { search } = useLocation();
-
-  return useMemo(() => new URLSearchParams(search), [search]);
-}
-
 function useSearchQuery(query: URLSearchParams) {
   const page = query.get("page") || 1;
   const search = query.get("search") || "";
@@ -79,7 +73,7 @@ const TableSelectable: React.FunctionComponent = () => {
   const { data, isLoading, isFetching } = useQuery({
     queryKey: ["feeds", searchFolderData],
     queryFn: () => fetchFeeds(searchFolderData),
-    enabled: isLoggedIn && type === "private",
+    enabled: type === "private",
   });
 
   const {
@@ -139,7 +133,7 @@ const TableSelectable: React.FunctionComponent = () => {
         activeItem: "analyses",
       }),
     );
-    if (bulkData && bulkData.current) {
+    if (bulkData?.current) {
       dispatch(removeAllSelect(bulkData.current));
     }
   }, [dispatch]);
@@ -187,7 +181,7 @@ const TableSelectable: React.FunctionComponent = () => {
             title={`New and Existing Analyses (${
               type === "private" && data && data.totalFeedsCount
                 ? data.totalFeedsCount
-                : publicFeeds && publicFeeds.totalFeedsCount
+                : publicFeeds?.totalFeedsCount
                   ? publicFeeds.totalFeedsCount
                   : 0
             })`}
@@ -286,6 +280,7 @@ const TableSelectable: React.FunctionComponent = () => {
                       bulkSelect={bulkSelect}
                       columnNames={columnNames}
                       allFeeds={feedsToDisplay}
+                      type={type}
                     />
                   );
                 })}
@@ -316,9 +311,16 @@ interface TableRowProps {
     size: string;
     status: string;
   };
+  type: string;
 }
 
-function TableRow({ feed, allFeeds, bulkSelect, columnNames }: TableRowProps) {
+function TableRow({
+  feed,
+  allFeeds,
+  bulkSelect,
+  columnNames,
+  type,
+}: TableRowProps) {
   const navigate = useNavigate();
   const [intervalMs, setIntervalMs] = React.useState(2000);
   const { isDarkTheme } = useContext(ThemeContext);
@@ -352,20 +354,19 @@ function TableRow({ feed, allFeeds, bulkSelect, columnNames }: TableRowProps) {
   const { id, name: feedName, creation_date, creator_username } = feed.data;
 
   const { dispatch } = usePaginate();
-  const progress = feedResources[id] && feedResources[id].details.progress;
+  const progress = feedResources[id]?.details.progress;
 
-  const size = feedResources[id] && feedResources[id].details.size;
-  const feedError = feedResources[id] && feedResources[id].details.error;
-  const runtime = feedResources[id] && feedResources[id].details.time;
+  const size = feedResources[id]?.details.size;
+  const feedError = feedResources[id]?.details.error;
+  const runtime = feedResources[id]?.details.time;
 
-  const feedProgressText =
-    feedResources[id] && feedResources[id].details.feedProgressText;
+  const feedProgressText = feedResources[id]?.details.feedProgressText;
 
   let threshold = Infinity;
 
   // If error in a feed => reflect in progres
 
-  let title = (progress ? progress : 0) + "%";
+  let title = `${progress ? progress : 0}%`;
   let color = "blue";
 
   if (feedError) {
@@ -374,7 +375,7 @@ function TableRow({ feed, allFeeds, bulkSelect, columnNames }: TableRowProps) {
   }
 
   // If initial node in a feed fails
-  if (progress == 0 && feedError) {
+  if (progress === 0 && feedError) {
     color = "#00ff00";
     title = "❌";
   }
@@ -384,7 +385,7 @@ function TableRow({ feed, allFeeds, bulkSelect, columnNames }: TableRowProps) {
     color = "#00ff00";
     threshold = progress;
   }
-  if (progress == 100) {
+  if (progress === 100) {
     title = "✔️";
   }
 
@@ -413,7 +414,7 @@ function TableRow({ feed, allFeeds, bulkSelect, columnNames }: TableRowProps) {
       <Button
         variant="link"
         onClick={() => {
-          navigate(`/feeds/${id}`);
+          navigate(`/feeds/${id}?type=${type}`);
         }}
       >
         {feedName}
diff --git a/src/components/Feeds/FeedView.tsx b/src/components/Feeds/FeedView.tsx
index b4d4248a2..726285288 100644
--- a/src/components/Feeds/FeedView.tsx
+++ b/src/components/Feeds/FeedView.tsx
@@ -34,8 +34,12 @@ import WrapperConnect from "../Wrapper";
 import { resetActiveResources } from "../../store/resources/actions";
 import FeedOutputBrowser from "../FeedOutputBrowser/FeedOutputBrowser";
 import { fetchAuthenticatedFeed, fetchPublicFeed } from "./utilties";
+import { useSearchQueryParams } from "./usePaginate";
 
 export default function FeedView() {
+  const query = useSearchQueryParams();
+  const type = query.get("type");
+
   const params = useParams();
   const dispatch = useDispatch();
   const navigate = useNavigate();
@@ -61,38 +65,36 @@ export default function FeedView() {
   const { data: publicFeed } = useQuery({
     queryKey: ["publicFeed", id],
     queryFn: () => fetchPublicFeed(id),
-    enabled: !isLoggedIn,
+    enabled: type === "public",
   });
 
-  const { data: feed } = useQuery({
+  const { data: privateFeed } = useQuery({
     queryKey: ["authenticatedFeed", id],
     queryFn: () => fetchAuthenticatedFeed(id),
-    enabled: isLoggedIn,
+    enabled: type === "private",
   });
 
   React.useEffect(() => {
-    if (isLoggedIn && feed) {
-      dispatch(getFeedSuccess(feed as Feed));
-      dispatch(getPluginInstancesRequest(feed));
+    if (!type) {
+      navigate("/feeds?type=public");
     }
+  }, [type, navigate]);
 
-    if (!isLoggedIn && publicFeed && Object.keys(publicFeed).length > 0) {
-      dispatch(getFeedSuccess(publicFeed as any as Feed));
-      dispatch(getPluginInstancesRequest(publicFeed as Feed));
+  React.useEffect(() => {
+    const feed: Feed | undefined = privateFeed || publicFeed;
+    if (feed) {
+      dispatch(getFeedSuccess(feed as Feed));
+      dispatch(getPluginInstancesRequest(feed));
     }
 
     if (!isLoggedIn && publicFeed && Object.keys(publicFeed).length === 0) {
       navigate("/feeds?type=public");
     }
-  }, [isLoggedIn, feed, publicFeed, dispatch, navigate]);
+  }, [isLoggedIn, privateFeed, publicFeed, dispatch, navigate]);
 
   React.useEffect(() => {
     return () => {
-      if (
-        dataRef.current &&
-        dataRef.current.selectedPlugin &&
-        dataRef.current.data
-      ) {
+      if (dataRef.current?.selectedPlugin && dataRef.current.data) {
         dispatch(resetActiveResources(dataRef.current));
       }
       dispatch(resetFeed());
diff --git a/src/components/Feeds/usePaginate.tsx b/src/components/Feeds/usePaginate.tsx
index 4674a69f9..875bc0ab0 100644
--- a/src/components/Feeds/usePaginate.tsx
+++ b/src/components/Feeds/usePaginate.tsx
@@ -1,4 +1,5 @@
-import { useState, useCallback } from "react";
+import { useState, useCallback, useMemo } from "react";
+import { useLocation } from "react-router";
 import { useDispatch } from "react-redux";
 import { debounce } from "lodash";
 
@@ -61,3 +62,9 @@ export const usePaginate = () => {
     dispatch,
   };
 };
+
+export function useSearchQueryParams() {
+  const { search } = useLocation();
+
+  return useMemo(() => new URLSearchParams(search), [search]);
+}
diff --git a/src/components/Feeds/utilties.ts b/src/components/Feeds/utilties.ts
index b961a14f2..2acf99a08 100644
--- a/src/components/Feeds/utilties.ts
+++ b/src/components/Feeds/utilties.ts
@@ -116,7 +116,5 @@ export async function fetchPublicFeed(id?: string) {
   if (publicFeed && publicFeed.getItems().length > 0) {
     //@ts-ignore
     return publicFeed.getItems()[0] as any as Feed;
-  } else {
-    return {};
   }
 }
diff --git a/src/components/LibraryCopy/FolderCard.tsx b/src/components/LibraryCopy/FolderCard.tsx
index 898cdcae2..72bdf121b 100644
--- a/src/components/LibraryCopy/FolderCard.tsx
+++ b/src/components/LibraryCopy/FolderCard.tsx
@@ -8,10 +8,10 @@ import {
 import { useQuery } from "@tanstack/react-query";
 import { Link } from "react-router-dom";
 import FaFolder from "@patternfly/react-icons/dist/esm/icons/folder-icon";
-import ChrisAPIClient from "../../api/chrisapiclient";
 
 import ExternalLinkSquareIcon from "@patternfly/react-icons/dist/esm/icons/external-link-square-alt-icon";
 import useLongPress, { elipses } from "./utils";
+import { fetchAuthenticatedFeed, fetchPublicFeed } from "../Feeds/utilties";
 
 function FolderCard({
   folder,
@@ -31,18 +31,30 @@ function FolderCard({
 
   const isRoot = folder.startsWith("feed");
 
-  const fetchFeedDetails = async (id: number) => {
+  const fetchFeedDetails = async (id: string) => {
     if (!id) return;
-    const client = ChrisAPIClient.getClient();
-    const feed = await client.getFeed(id);
-    return feed;
+
+    const publicFeed = await fetchPublicFeed(id);
+
+    if (publicFeed) {
+      return {
+        feed: publicFeed,
+        type: "public",
+      };
+    }
+    const authenticatedFeed = await fetchAuthenticatedFeed(id);
+
+    return {
+      feed: authenticatedFeed,
+      type: "private",
+    };
   };
 
   const id = folder.split("_")[1];
 
-  const { data: feed } = useQuery({
+  const { data } = useQuery({
     queryKey: ["feed", id],
-    queryFn: () => fetchFeedDetails(+id),
+    queryFn: () => fetchFeedDetails(id),
     enabled: !!folder.startsWith("feed"),
   });
 
@@ -58,15 +70,14 @@ function FolderCard({
     >
       <CardHeader
         actions={{
-          actions:
-            feed && feed.data.id ? (
-              <span>
-                <Link to={`/feeds/${feed.data.id}`}>
-                  {" "}
-                  <ExternalLinkSquareIcon />
-                </Link>
-              </span>
-            ) : null,
+          actions: data?.feed?.data.id ? (
+            <span>
+              <Link to={`/feeds/${data?.feed?.data.id}?type=${data?.type}`}>
+                {" "}
+                <ExternalLinkSquareIcon />
+              </Link>
+            </span>
+          ) : null,
         }}
       >
         <Split style={{ overflow: "hidden" }}>
@@ -83,10 +94,10 @@ function FolderCard({
                 }
               }}
             >
-              {feed ? elipses(feed.data.name, 40) : elipses(folder, 40)}
+              {data ? elipses(data?.feed?.data.name, 40) : elipses(folder, 40)}
             </Button>
             <div>
-              {feed && new Date(feed.data.creation_date).toDateString()}
+              {data && new Date(data?.feed?.data.creation_date).toDateString()}
             </div>
           </SplitItem>
         </Split>
diff --git a/src/components/Login/index.tsx b/src/components/Login/index.tsx
index 6a8d32730..1c8a50d56 100644
--- a/src/components/Login/index.tsx
+++ b/src/components/Login/index.tsx
@@ -19,6 +19,7 @@ import {
 import ExclamationCircleIcon from "@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon";
 import { setAuthTokenSuccess } from "../../store/user/actions";
 import "./Login.css";
+import ChrisAPIClient from "../../api/chrisapiclient";
 
 export const SimpleLoginPage: React.FunctionComponent = () => {
   const dispatch = useDispatch();
@@ -33,8 +34,8 @@ export const SimpleLoginPage: React.FunctionComponent = () => {
   const navigate = useNavigate();
 
   enum LoginErrorMessage {
-    invalidCredentials = `Invalid Credentials`,
-    serverError = `There was a problem connecting to the server!`,
+    invalidCredentials = "Invalid Credentials",
+    serverError = "There was a problem connecting to the server!",
   }
 
   async function handleSubmit(
@@ -45,10 +46,34 @@ export const SimpleLoginPage: React.FunctionComponent = () => {
     event.preventDefault();
 
     const authURL = import.meta.env.VITE_CHRIS_UI_AUTH_URL;
-    let token;
+    let token: string;
 
     try {
       token = await ChrisApiClient.getAuthToken(authURL, username, password);
+      if (token && username) {
+        const oneDayToSeconds = 24 * 60 * 60;
+        setCookie(`${username}_token`, token, {
+          path: "/",
+          maxAge: oneDayToSeconds,
+        });
+        setCookie("username", username, {
+          path: "/",
+          maxAge: oneDayToSeconds,
+        });
+
+        const client = ChrisAPIClient.getClient();
+        const user = await client.getUser();
+
+        dispatch(
+          setAuthTokenSuccess({
+            token,
+            username: username,
+            isStaff: user.data.is_staff,
+          }),
+        );
+
+        navigate("/");
+      }
     } catch (error: unknown) {
       setShowHelperText(true);
       // Allows error message to be displayed in red
@@ -62,25 +87,6 @@ export const SimpleLoginPage: React.FunctionComponent = () => {
           : LoginErrorMessage.serverError,
       );
     }
-
-    if (token && username) {
-      dispatch(
-        setAuthTokenSuccess({
-          token,
-          username: username,
-        }),
-      );
-      const oneDayToSeconds = 24 * 60 * 60;
-      setCookie(`${username}_token`, token, {
-        path: "/",
-        maxAge: oneDayToSeconds,
-      });
-      setCookie("username", username, {
-        path: "/",
-        maxAge: oneDayToSeconds,
-      });
-      navigate("/");
-    }
   }
 
   const handleUsernameChange = (
diff --git a/src/components/Signup/SignUpForm.tsx b/src/components/Signup/SignUpForm.tsx
index 38687dc5f..c07fa5b22 100644
--- a/src/components/Signup/SignUpForm.tsx
+++ b/src/components/Signup/SignUpForm.tsx
@@ -14,7 +14,7 @@ import {
   HelperText,
   HelperTextItem,
 } from "@patternfly/react-core";
-import ChrisApiClient from "@fnndsc/chrisapi";
+import ChrisApiClient, { User } from "@fnndsc/chrisapi";
 import { Link } from "react-router-dom";
 import { has } from "lodash";
 import { validate } from "email-validator";
@@ -27,7 +27,11 @@ type Validated = {
 };
 
 interface SignUpFormProps {
-  setAuthTokenSuccess: (auth: { token: string; username: string }) => void;
+  setAuthTokenSuccess: (auth: {
+    token: string;
+    username: string;
+    isStaff: boolean;
+  }) => void;
   isShowPasswordEnabled?: boolean;
   showPasswordAriaLabel?: string;
   hidePasswordAriaLabel?: string;
@@ -104,8 +108,8 @@ const SignUpForm: React.FC<SignUpFormProps> = ({
     setLoading(true);
     const userURL = import.meta.env.VITE_CHRIS_UI_USERS_URL;
     const authURL = import.meta.env.VITE_CHRIS_UI_AUTH_URL;
-    let user;
-    let token;
+    let user: User;
+    let token: string;
 
     if (userURL) {
       try {
@@ -121,6 +125,26 @@ const SignUpForm: React.FC<SignUpFormProps> = ({
           userState.username,
           passwordState.password,
         );
+
+        if (user && token) {
+          const oneDayToSeconds = 24 * 60 * 60;
+          setCookie("username", user.data.username, {
+            path: "/",
+            maxAge: oneDayToSeconds,
+          });
+          setCookie(`${user.data.username}_token`, token, {
+            path: "/",
+            maxAge: oneDayToSeconds,
+          });
+          setAuthTokenSuccess({
+            token,
+            username: user.data.username,
+            isStaff: user.data.is_staff,
+          });
+          const then = new URLSearchParams(location.search).get("then");
+          if (then) navigate(then);
+          else navigate("/");
+        }
       } catch (error) {
         if (has(error, "response")) {
           if (has(error, "response.data.username")) {
@@ -157,25 +181,6 @@ const SignUpForm: React.FC<SignUpFormProps> = ({
         }
       }
     }
-
-    if (user && token) {
-      const oneDayToSeconds = 24 * 60 * 60;
-      setCookie("username", user.data.username, {
-        path: "/",
-        maxAge: oneDayToSeconds,
-      });
-      setCookie(`${user.data.username}_token`, token, {
-        path: "/",
-        maxAge: oneDayToSeconds,
-      });
-      setAuthTokenSuccess({
-        token,
-        username: user.data.username,
-      });
-      const then = new URLSearchParams(location.search).get("then");
-      if (then) navigate(then);
-      else navigate("/");
-    }
   };
 
   const passwordInput = (
@@ -225,7 +230,7 @@ const SignUpForm: React.FC<SignUpFormProps> = ({
               username: value,
             })
           }
-        ></TextInput>
+        />
         <HelperText>
           <HelperTextItem variant="error">
             {userState.invalidText}
@@ -302,8 +307,11 @@ const SignUpForm: React.FC<SignUpFormProps> = ({
 };
 
 const mapDispatchToProps = (dispatch: Dispatch) => ({
-  setAuthTokenSuccess: (auth: { token: string; username: string }) =>
-    dispatch(setAuthTokenSuccess(auth)),
+  setAuthTokenSuccess: (auth: {
+    token: string;
+    username: string;
+    isStaff: boolean;
+  }) => dispatch(setAuthTokenSuccess(auth)),
 });
 
 export default connect(null, mapDispatchToProps)(SignUpForm);
diff --git a/src/store/feed/reducer.ts b/src/store/feed/reducer.ts
index 4e8e8c14d..0e847d9c4 100644
--- a/src/store/feed/reducer.ts
+++ b/src/store/feed/reducer.ts
@@ -67,15 +67,14 @@ const reducer: Reducer<IFeedState> = (
             orientation: "vertical",
           },
         };
-      else {
-        return {
-          ...state,
-          feedTreeProp: {
-            ...state.feedTreeProp,
-            orientation: "horizontal",
-          },
-        };
-      }
+
+      return {
+        ...state,
+        feedTreeProp: {
+          ...state.feedTreeProp,
+          orientation: "horizontal",
+        },
+      };
     }
 
     case FeedActionTypes.SET_ALL_SELECT: {
diff --git a/src/store/user/actions.ts b/src/store/user/actions.ts
index 1f94de2a2..b1b0bc3ca 100644
--- a/src/store/user/actions.ts
+++ b/src/store/user/actions.ts
@@ -4,6 +4,7 @@ import { UserActionTypes } from "./types";
 export const setAuthTokenSuccess = (auth: {
   token: string;
   username: string;
+  isStaff: boolean;
 }) => action(UserActionTypes.SET_TOKEN_SUCCESS, auth); // NOTE: To be done: Save user token to cookie or session
 export const setUserLogout = (username: string) =>
   action(UserActionTypes.LOGOUT_USER, username);
diff --git a/src/store/user/reducer.ts b/src/store/user/reducer.ts
index bc0fea80c..c84d2231a 100644
--- a/src/store/user/reducer.ts
+++ b/src/store/user/reducer.ts
@@ -12,6 +12,7 @@ const initialState: IUserState = {
   token: token,
   isRememberMe: false,
   isLoggedIn: token ? true : false,
+  isStaff: false,
 };
 
 // ***** NOTE: Working *****
@@ -26,6 +27,7 @@ const reducer: Reducer<IUserState> = (
         username: action.payload.username,
         token: action.payload.token,
         isLoggedIn: true,
+        isStaff: action.payload.isStaff,
       };
     }
     case UserActionTypes.SET_TOKEN_ERROR: {
diff --git a/src/store/user/types.ts b/src/store/user/types.ts
index f97e984c6..42d100693 100644
--- a/src/store/user/types.ts
+++ b/src/store/user/types.ts
@@ -14,6 +14,7 @@ export interface IUserState {
   token?: string | null;
   isRememberMe?: boolean;
   isLoggedIn?: boolean;
+  isStaff: boolean;
 }
 
 export const UserActionTypes = keyMirror({

From b5154d122acc0d122c480648d4136ea68b945fa8 Mon Sep 17 00:00:00 2001
From: PintoGideon <gideonpinto123@gmail.com>
Date: Thu, 15 Feb 2024 15:04:16 -0500
Subject: [PATCH 2/3] Adding a loading spinner while loading the tree

---
 src/components/FeedTree/ParentComponent.tsx | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/components/FeedTree/ParentComponent.tsx b/src/components/FeedTree/ParentComponent.tsx
index d3b8649ee..a4a7b5d52 100644
--- a/src/components/FeedTree/ParentComponent.tsx
+++ b/src/components/FeedTree/ParentComponent.tsx
@@ -1,10 +1,12 @@
 import React from "react";
+import { Alert } from "antd";
 import { useDispatch } from "react-redux";
-import { setFeedTreeProp } from "../../store/feed/actions";
 import { PluginInstance } from "@fnndsc/chrisapi";
+import { setFeedTreeProp } from "../../store/feed/actions";
 import FeedTree from "./FeedTree";
 import { getFeedTree, TreeNodeDatum, getTsNodes } from "./data";
 import { useTypedSelector } from "../../store/hooks";
+import { SpinContainer } from "../Common";
 import "./FeedTree.css";
 
 interface ParentComponentProps {
@@ -60,11 +62,9 @@ const ParentComponent = (props: ParentComponentProps) => {
       changeOrientation={changeOrientation}
     />
   ) : loading ? (
-    <div>Loading...</div>
+    <SpinContainer title="Loading the tree" />
   ) : error ? (
-    <div className="feed-tree">
-      <div>There was an error</div>
-    </div>
+    <Alert type="error" description={error} />
   ) : null;
 };
 

From 1662f76d76ad6a9bf9faaaacac94a9eef78a235e Mon Sep 17 00:00:00 2001
From: PintoGideon <gideonpinto123@gmail.com>
Date: Thu, 15 Feb 2024 15:17:19 -0500
Subject: [PATCH 3/3] Providing a better experience of the context switching
 between public and private feeds when a user sign's in and out

---
 src/components/Feeds/FeedListView.tsx | 5 ++---
 src/components/Feeds/FeedView.tsx     | 4 ++--
 src/components/Wrapper/Sidebar.tsx    | 2 +-
 3 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/src/components/Feeds/FeedListView.tsx b/src/components/Feeds/FeedListView.tsx
index a2d2880f3..0c7f90b62 100644
--- a/src/components/Feeds/FeedListView.tsx
+++ b/src/components/Feeds/FeedListView.tsx
@@ -139,10 +139,9 @@ const TableSelectable: React.FunctionComponent = () => {
   }, [dispatch]);
 
   React.useEffect(() => {
-    if (!type) {
-      const feedType = isLoggedIn ? "private" : "public";
+    if (!type || (!isLoggedIn && type === "private")) {
       navigate(
-        `/feeds?search=${search}&searchType=${searchType}&page=${page}&perPage=${perPage}&type=${feedType}`,
+        `/feeds?search=${search}&searchType=${searchType}&page=${page}&perPage=${perPage}&type=public`,
       );
     }
   }, [isLoggedIn, navigate, perPage, page, searchType, search, type]);
diff --git a/src/components/Feeds/FeedView.tsx b/src/components/Feeds/FeedView.tsx
index 726285288..26d5c096e 100644
--- a/src/components/Feeds/FeedView.tsx
+++ b/src/components/Feeds/FeedView.tsx
@@ -75,10 +75,10 @@ export default function FeedView() {
   });
 
   React.useEffect(() => {
-    if (!type) {
+    if (!type || (type === "private" && !isLoggedIn)) {
       navigate("/feeds?type=public");
     }
-  }, [type, navigate]);
+  }, [type, navigate, isLoggedIn]);
 
   React.useEffect(() => {
     const feed: Feed | undefined = privateFeed || publicFeed;
diff --git a/src/components/Wrapper/Sidebar.tsx b/src/components/Wrapper/Sidebar.tsx
index dcdf8127f..687fc5fb0 100644
--- a/src/components/Wrapper/Sidebar.tsx
+++ b/src/components/Wrapper/Sidebar.tsx
@@ -137,7 +137,7 @@ const AnonSidebarImpl: React.FC<AllProps> = ({
             itemId="analyses"
             isActive={sidebarActiveItem === "analyses"}
           >
-            <Link to="/feeds">New and Existing Analyses</Link>
+            <Link to="/feeds?type=public">New and Existing Analyses</Link>
           </NavItem>
           <NavItem itemId="catalog" isActive={sidebarActiveItem === "catalog"}>
             <Link to="/catalog">Plugins</Link>