This is an automated email from the ASF dual-hosted git repository.

ganning pushed a commit to branch delete-all-button
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git

commit f598f2f32ff7f080905f6b35106cb2684bb95379
Author: ganning127 <[email protected]>
AuthorDate: Fri Aug 1 00:03:17 2025 -0700

    Frontend support for deleting all sessions button
---
 .../src/components/home/DeleteAllSessions.tsx      |  86 +++++++++++
 .../src/components/home/SessionCard.tsx            | 170 ++++++++++-----------
 .../src/components/home/SessionsSection.tsx        |  94 ++++++------
 3 files changed, 221 insertions(+), 129 deletions(-)

diff --git a/airavata-research-portal/src/components/home/DeleteAllSessions.tsx 
b/airavata-research-portal/src/components/home/DeleteAllSessions.tsx
new file mode 100644
index 000000000..e9cfe9038
--- /dev/null
+++ b/airavata-research-portal/src/components/home/DeleteAllSessions.tsx
@@ -0,0 +1,86 @@
+import {SessionType} from "@/interfaces/SessionType.tsx";
+import {Button, CloseButton, Dialog, Input, Portal, Text, useDialog} from 
"@chakra-ui/react";
+import {useState} from "react";
+import {toaster} from "@/components/ui/toaster.tsx";
+import {CONTROLLER} from "@/lib/controller.ts";
+import api from "@/lib/api.ts";
+
+export const DeleteAllSessions = ({
+                                    sessions,
+                                    setSessions
+                                  }: {
+  sessions: SessionType[],
+  setSessions: (sessions: SessionType[]) => void
+}) => {
+  const dialog = useDialog();
+  const [loading, setLoading] = useState(false);
+  const [deleteName, setDeleteName] = useState("");
+  const handleDelete = async () => {
+    setLoading(true);
+
+    try {
+      const ids = sessions.map(s => s.id);
+      await api.delete(`${CONTROLLER.sessions}/delete/${ids.join(",")}`);
+      setSessions([]);
+    } catch {
+      toaster.create({
+        'title': 'Error',
+        'description': 'Failed to delete all sessions',
+        'type': 'error'
+      })
+    } finally {
+      setLoading(false);
+    }
+  }
+  return (
+      <>
+        <Dialog.RootProvider size="sm" value={dialog}>
+          <Portal>
+            <Dialog.Backdrop/>
+            <Dialog.Positioner>
+              <Dialog.Content>
+                <Dialog.Header>
+                  <Dialog.Title>Delete All Sessions</Dialog.Title>
+                </Dialog.Header>
+                <Dialog.Body>
+                  <Text color="gray.500">
+                    This action is irreversible. To confirm, please type:{" "}
+                    <b>delete all sessions</b>.
+                  </Text>
+
+                  <Input
+                      mt={2}
+                      placeholder="Session name"
+                      value={deleteName}
+                      onChange={(e) => setDeleteName(e.target.value)}
+                  />
+                </Dialog.Body>
+                <Dialog.Footer>
+                  <Button
+                      width="100%"
+                      colorPalette="red"
+                      disabled={deleteName !== "delete all sessions" || 
loading}
+                      loading={loading}
+                      onClick={handleDelete}
+                  >
+                    Delete
+                  </Button>
+                </Dialog.Footer>
+                <Dialog.CloseTrigger asChild>
+                  <CloseButton size="sm"/>
+                </Dialog.CloseTrigger>
+              </Dialog.Content>
+            </Dialog.Positioner>
+          </Portal>
+        </Dialog.RootProvider>
+
+        <Button
+            colorPalette={'red'}
+            loading={loading}
+            onClick={() => dialog.setOpen(true)}
+        >
+          Delete All
+        </Button>
+      </>
+  )
+}
\ No newline at end of file
diff --git a/airavata-research-portal/src/components/home/SessionCard.tsx 
b/airavata-research-portal/src/components/home/SessionCard.tsx
index 2bd14a2d9..8f9add93a 100644
--- a/airavata-research-portal/src/components/home/SessionCard.tsx
+++ b/airavata-research-portal/src/components/home/SessionCard.tsx
@@ -1,26 +1,26 @@
-import { SessionType } from "@/interfaces/SessionType";
+import {SessionType} from "@/interfaces/SessionType";
 import {
+  Badge,
   Box,
-  Text,
+  Button,
   Card,
+  CloseButton,
+  Dialog,
   Heading,
   HStack,
-  Badge,
-  VStack,
   IconButton,
-  Dialog,
-  useDialog,
-  CloseButton,
-  Portal,
-  Button,
   Input,
+  Portal,
+  Text,
+  useDialog,
+  VStack,
 } from "@chakra-ui/react";
-import { SessionCardControls } from "./SessionCardControls";
-import { SessionStatusEnum } from "@/interfaces/SessionStatusEnum";
-import { FaTrash } from "react-icons/fa";
-import { toaster } from "../ui/toaster";
-import { CONTROLLER } from "@/lib/controller";
-import { useState } from "react";
+import {SessionCardControls} from "./SessionCardControls";
+import {SessionStatusEnum} from "@/interfaces/SessionStatusEnum";
+import {FaTrash} from "react-icons/fa";
+import {toaster} from "../ui/toaster";
+import {CONTROLLER} from "@/lib/controller";
+import {useState} from "react";
 import api from "@/lib/api";
 
 const getColorPalette = (status: string) => {
@@ -40,7 +40,7 @@ const getColorPalette = (status: string) => {
   }
 };
 
-export const SessionCard = ({ session }: { session: SessionType }) => {
+export const SessionCard = ({session}: { session: SessionType }) => {
   const dialog = useDialog();
   const [hideCard, setHideCard] = useState(false);
   const [deleteName, setDeleteName] = useState("");
@@ -68,81 +68,79 @@ export const SessionCard = ({ session }: { session: 
SessionType }) => {
   };
 
   return (
-    <>
-      <Dialog.RootProvider size="sm" value={dialog}>
-        <Portal>
-          <Dialog.Backdrop />
-          <Dialog.Positioner>
-            <Dialog.Content>
-              <Dialog.Header>
-                <Dialog.Title>Delete Session</Dialog.Title>
-              </Dialog.Header>
-              <Dialog.Body>
-                <Text color="gray.500">
-                  This action is irreversible. To confirm, please type:{" "}
-                  <b>{session.sessionName}</b>.
-                </Text>
+      <>
+        <Dialog.RootProvider size="sm" value={dialog}>
+          <Portal>
+            <Dialog.Backdrop/>
+            <Dialog.Positioner>
+              <Dialog.Content>
+                <Dialog.Header>
+                  <Dialog.Title>Delete Session</Dialog.Title>
+                </Dialog.Header>
+                <Dialog.Body>
+                  <Text color="gray.500">
+                    This action is irreversible. To confirm, please type:{" "}
+                    <b>{session.sessionName}</b>.
+                  </Text>
 
-                <Input
-                  mt={2}
-                  placeholder="Session name"
-                  value={deleteName}
-                  onChange={(e) => setDeleteName(e.target.value)}
-                />
-              </Dialog.Body>
-              <Dialog.Footer>
-                <Button
-                  width="100%"
-                  colorPalette="red"
-                  disabled={deleteName !== session.sessionName || 
deleteLoading}
-                  loading={deleteLoading}
-                  onClick={handleDeleteSession}
-                >
-                  Delete
-                </Button>
-              </Dialog.Footer>
-              <Dialog.CloseTrigger asChild>
-                <CloseButton size="sm" />
-              </Dialog.CloseTrigger>
-            </Dialog.Content>
-          </Dialog.Positioner>
-        </Portal>
-      </Dialog.RootProvider>
+                  <Input
+                      mt={2}
+                      placeholder="Session name"
+                      value={deleteName}
+                      onChange={(e) => setDeleteName(e.target.value)}
+                  />
+                </Dialog.Body>
+                <Dialog.Footer>
+                  <Button
+                      width="100%"
+                      colorPalette="red"
+                      disabled={deleteName !== session.sessionName || 
deleteLoading}
+                      loading={deleteLoading}
+                      onClick={handleDeleteSession}
+                  >
+                    Delete
+                  </Button>
+                </Dialog.Footer>
+                <Dialog.CloseTrigger asChild>
+                  <CloseButton size="sm"/>
+                </Dialog.CloseTrigger>
+              </Dialog.Content>
+            </Dialog.Positioner>
+          </Portal>
+        </Dialog.RootProvider>
 
-      <Card.Root size="sm" height="fit-content" hidden={hideCard}>
-        <Card.Header>
-          <HStack justify="space-between" alignItems="flex-start">
-            <Box>
-              <Heading size="lg">{session.sessionName}</Heading>
-            </Box>
-            <VStack alignItems="flex-end">
-              {session.status === SessionStatusEnum.TERMINATED && (
+        <Card.Root size="sm" height="fit-content" hidden={hideCard}>
+          <Card.Header>
+            <HStack justify="space-between" alignItems="flex-start">
+              <Box>
+                <Heading size="lg">{session.sessionName}</Heading>
+              </Box>
+              <VStack alignItems="flex-end">
                 <IconButton
-                  color="red.600"
-                  size="xs"
-                  variant={"ghost"}
-                  onClick={() => dialog.setOpen(true)}
+                    color="red.600"
+                    size="xs"
+                    variant={"ghost"}
+                    onClick={() => dialog.setOpen(true)}
                 >
-                  <FaTrash />
+                  <FaTrash/>
                 </IconButton>
-              )}
-            </VStack>
-          </HStack>
-        </Card.Header>
-        <Card.Body>
-          <HStack alignItems={"center"} gap={1}>
-            <Badge size="md" colorPalette={getColorPalette(session.status)}>
-              {session.status}
-            </Badge>
-            •
-            <Text color="fg.muted">
-              {new Date(session.createdAt).toLocaleString()}
-            </Text>
-          </HStack>
+              </VStack>
+            </HStack>
+          </Card.Header>
+          <Card.Body>
+            <HStack alignItems={"center"} gap={1}>
+              <Badge size="md" colorPalette={getColorPalette(session.status)}>
+                {session.status}
+              </Badge>
+              •
+              <Text color="fg.muted">
+                {new Date(session.createdAt).toLocaleString()}
+              </Text>
+            </HStack>
 
-          <SessionCardControls session={session} />
-        </Card.Body>
-      </Card.Root>
-    </>
+            <SessionCardControls session={session}/>
+          </Card.Body>
+        </Card.Root>
+      </>
   );
 };
diff --git a/airavata-research-portal/src/components/home/SessionsSection.tsx 
b/airavata-research-portal/src/components/home/SessionsSection.tsx
index 1b3de8aca..baf6c27ce 100644
--- a/airavata-research-portal/src/components/home/SessionsSection.tsx
+++ b/airavata-research-portal/src/components/home/SessionsSection.tsx
@@ -1,10 +1,11 @@
-import { SessionType } from "@/interfaces/SessionType";
-import { SessionCard } from "./SessionCard";
+import {SessionType} from "@/interfaces/SessionType";
+import {SessionCard} from "./SessionCard";
 import api from "@/lib/api";
-import { useEffect, useState } from "react";
-import { CONTROLLER } from "@/lib/controller";
-import { Button, HStack, SimpleGrid, Text } from "@chakra-ui/react";
-import { SessionStatusEnum } from "@/interfaces/SessionStatusEnum";
+import {useEffect, useState} from "react";
+import {CONTROLLER} from "@/lib/controller";
+import {Button, HStack, SimpleGrid, Text} from "@chakra-ui/react";
+import {SessionStatusEnum} from "@/interfaces/SessionStatusEnum";
+import {DeleteAllSessions} from "@/components/home/DeleteAllSessions.tsx";
 
 async function getSessions(status: SessionStatusEnum | null = null) {
   try {
@@ -26,7 +27,7 @@ async function getSessions(status: SessionStatusEnum | null = 
null) {
 
 export const SessionsSection = () => {
   const [sessionStatusFilter, setSessionStatusFilter] =
-    useState<SessionStatusEnum | null>(null);
+      useState<SessionStatusEnum | null>(null);
   const [sessions, setSessions] = useState<SessionType[]>([]);
 
   useEffect(() => {
@@ -54,42 +55,49 @@ export const SessionsSection = () => {
   ];
 
   return (
-    <>
-      <HStack flexWrap={"wrap"} gap={2} mt={4}>
-        <Button
-          variant={sessionStatusFilter === null ? "solid" : "outline"}
-          size="sm"
-          onClick={() => {
-            setSessionStatusFilter(null);
-          }}
-        >
-          All
-        </Button>
-        {/* {Object.values(SessionStatusEnum).map((status) => ( */}
-        {sessionStatusFilterOptions.map((status) => (
-          <Button
-            key={status}
-            variant={sessionStatusFilter === status ? "solid" : "outline"}
-            size="sm"
-            onClick={() => {
-              setSessionStatusFilter(status);
-            }}
-          >
-            {status}
-          </Button>
-        ))}
-      </HStack>
-      <SimpleGrid mt={4} columns={{ base: 1, md: 2, lg: 3 }} gap={4}>
-        {sessions.map((session: SessionType) => {
-          return <SessionCard key={session.id} session={session} />;
-        })}
-      </SimpleGrid>
+      <>
+        <HStack flexWrap={"wrap"} mt={4} justifyContent={'space-between'}>
+          <HStack flexWrap={"wrap"} gap={2}>
+            <Button
+                variant={sessionStatusFilter === null ? "solid" : "outline"}
+                size="sm"
+                onClick={() => {
+                  setSessionStatusFilter(null);
+                }}
+            >
+              All
+            </Button>
+            {/* {Object.values(SessionStatusEnum).map((status) => ( */}
+            {sessionStatusFilterOptions.map((status) => (
+                <Button
+                    key={status}
+                    variant={sessionStatusFilter === status ? "solid" : 
"outline"}
+                    size="sm"
+                    onClick={() => {
+                      setSessionStatusFilter(status);
+                    }}
+                >
+                  {status}
+                </Button>
+            ))}
+          </HStack>
+          {
+              sessions.length > 0 && (
+                  <DeleteAllSessions sessions={sessions} 
setSessions={setSessions}/>
+              )
+          }
+        </HStack>
+        <SimpleGrid mt={4} columns={{base: 1, md: 2, lg: 3}} gap={4}>
+          {sessions.map((session: SessionType) => {
+            return <SessionCard key={session.id} session={session}/>;
+          })}
+        </SimpleGrid>
 
-      {sessions.length === 0 && (
-        <Text mt={4} color="gray.500">
-          No sessions found.
-        </Text>
-      )}
-    </>
+        {sessions.length === 0 && (
+            <Text mt={4} color="gray.500">
+              No sessions found.
+            </Text>
+        )}
+      </>
   );
 };

Reply via email to