From 5a5830b383ca30634e72729a016fda25fe65b685 Mon Sep 17 00:00:00 2001 From: Jacqueline Amherst Date: Wed, 16 Apr 2025 14:44:23 -0500 Subject: [PATCH 01/26] move components into more specific directories --- src/app/components/NewAlarmModal.tsx | 2 +- src/app/components/SlackModal.tsx | 2 +- src/app/instances/[name]/backups/page.tsx | 2 +- .../instances/[name]/configuration/page.tsx | 27 ++- src/app/instances/[name]/firewall/page.tsx | 58 +++---- .../[name]/hardware/InstanceTypePage.tsx | 4 +- .../instances/[name]/hardware/StoragePage.tsx | 15 +- .../components/ErrorBanner.tsx | 0 .../components/InstanceDetails.tsx | 0 .../components/StorageDetails.tsx | 0 .../components/SubmissionSpinner.tsx | 0 .../NewInstanceLoadingSkeleton.tsx | 0 src/app/instances/new/page.tsx | 34 ++-- src/app/instances/page.tsx | 158 +++++++++--------- 14 files changed, 149 insertions(+), 153 deletions(-) rename src/app/{ => instances}/components/ErrorBanner.tsx (100%) rename src/app/{ => instances}/components/InstanceDetails.tsx (100%) rename src/app/{ => instances}/components/StorageDetails.tsx (100%) rename src/app/{ => instances}/components/SubmissionSpinner.tsx (100%) rename src/app/instances/new/{ => components}/NewInstanceLoadingSkeleton.tsx (100%) diff --git a/src/app/components/NewAlarmModal.tsx b/src/app/components/NewAlarmModal.tsx index 6717af5..70dcedf 100644 --- a/src/app/components/NewAlarmModal.tsx +++ b/src/app/components/NewAlarmModal.tsx @@ -1,6 +1,6 @@ import { useInstanceContext } from "../instances/[name]/InstanceContext"; import { useState } from "react"; -import ErrorBanner from "@/app/components/ErrorBanner"; +import ErrorBanner from "@/app/instances/components/ErrorBanner"; import axios from "axios"; interface Props { diff --git a/src/app/components/SlackModal.tsx b/src/app/components/SlackModal.tsx index bc41260..4576e17 100644 --- a/src/app/components/SlackModal.tsx +++ b/src/app/components/SlackModal.tsx @@ -1,7 +1,7 @@ import { useInstanceContext } from "../instances/[name]/InstanceContext"; import { useState } from "react"; import axios from "axios"; -import ErrorBanner from "@/app/components/ErrorBanner"; +import ErrorBanner from "@/app/instances/components/ErrorBanner"; interface Props { url: string; diff --git a/src/app/instances/[name]/backups/page.tsx b/src/app/instances/[name]/backups/page.tsx index 033446f..0a174bd 100644 --- a/src/app/instances/[name]/backups/page.tsx +++ b/src/app/instances/[name]/backups/page.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; import { useInstanceContext } from "../InstanceContext"; -import SubmissionSpinner from "@/app/components/SubmissionSpinner"; +import SubmissionSpinner from "../../components/SubmissionSpinner"; import { useNotificationsContext } from "@/app/NotificationContext"; interface Backup { diff --git a/src/app/instances/[name]/configuration/page.tsx b/src/app/instances/[name]/configuration/page.tsx index e8d2e1c..b86521e 100644 --- a/src/app/instances/[name]/configuration/page.tsx +++ b/src/app/instances/[name]/configuration/page.tsx @@ -6,9 +6,9 @@ import { useInstanceContext } from "../InstanceContext"; import { useNotificationsContext } from "@/app/NotificationContext"; import { configItems } from "@/types/configuration"; import { validateConfiguration } from "@/utils/validateConfig"; -import ErrorBanner from "@/app/components/ErrorBanner"; +import ErrorBanner from "@/app/instances/components/ErrorBanner"; import Link from "next/link"; -import SubmissionSpinner from "@/app/components/SubmissionSpinner"; +import SubmissionSpinner from "../../components/SubmissionSpinner"; interface Configuration { [key: string]: string; @@ -50,12 +50,12 @@ export default function ConfigurationPage() { e.preventDefault(); const validationErrors = validateConfiguration(configuration); setErrors(validationErrors); - + if (validationErrors.length > 0) { configSectionRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }); return; } - + if (!instance || !instance.name) { throw new Error("No instance found"); }; @@ -80,14 +80,14 @@ export default function ConfigurationPage() { console.error("Error saving configuration:", error); } }; - + const resetError = (msg: string) => { setErrors((prev) => prev.filter((e) => e !== msg)); }; return ( -
@@ -183,16 +183,15 @@ export default function ConfigurationPage() { diff --git a/src/app/instances/[name]/firewall/page.tsx b/src/app/instances/[name]/firewall/page.tsx index 699466c..db7be00 100644 --- a/src/app/instances/[name]/firewall/page.tsx +++ b/src/app/instances/[name]/firewall/page.tsx @@ -5,14 +5,14 @@ import axios from "axios"; import React from "react"; import { useInstanceContext } from "../InstanceContext"; import { FirewallRule } from "@/types/firewall"; -import ErrorBanner from "@/app/components/ErrorBanner"; +import ErrorBanner from "@/app/instances/components/ErrorBanner"; import { isValidDescription, isValidSourceIp, isInRangeCustomPort } from "@/utils/firewallValidation"; import { COMMON_PORTS } from "@/utils/firewallConstants"; import { Info } from "lucide-react"; import { Trash2 } from "lucide-react"; import { useNotificationsContext } from "@/app/NotificationContext"; -import SubmissionSpinner from "@/app/components/SubmissionSpinner"; +import SubmissionSpinner from "../../components/SubmissionSpinner"; export default function FirewallPage() { const { instance } = useInstanceContext(); @@ -59,15 +59,15 @@ export default function FirewallPage() { setRules((prevRules) => { const updatedRules = [...prevRules]; updatedRules[index] = { ...updatedRules[index], description: value }; - + const error = validateDescription(value); resetError(`Description must be 255 characters or fewer, and cannot contain the following characters: ^, ", ', %, &, <, >, |, \``); - + if (error) addError(error); return updatedRules; }); }; - + const handleSourceIpChange = (index: number, value: string) => { setRules((prevRules) => { const updatedRules = [...prevRules]; @@ -83,7 +83,7 @@ export default function FirewallPage() { resetError("Invalid Source IP format."); if (error) addError(error); }; - + const handleCustomPortsChange = (index: number, value: string) => { setRules((prevRules) => { const updatedRules = [...prevRules]; @@ -143,51 +143,51 @@ export default function FirewallPage() { } return null; } - + const validateSourceIp = (sourceIp: string): string | null => { const inputError = "Invalid Source IP format. Use CIDR block notation, e.g. '192.168.0.0/24' or '10.0.0.1/32'."; - + if (sourceIp.trim() !== "" && !isValidSourceIp(sourceIp)) { return inputError; } - + return null; }; - + const validateCustomPorts = (customPorts: string, commonPorts: string[]): string[] => { const errors: string[] = []; - + const portList = customPorts .split(",") .map((port) => port.trim()) .filter((port) => port !== ""); - + const nonNumericPorts = portList.filter((port) => !/^\d+$/.test(port)); - + if (nonNumericPorts.length > 0) { errors.push("Custom ports must be a comma-separated list of numbers."); return errors; } - + if (!isInRangeCustomPort(customPorts)) { errors.push("Ports must be between 1 and 65535."); } - + const repeated = findCommonAndCustomPortOverlap(customPorts, commonPorts); if (repeated.length > 0) { errors.push("Port is already listed as a common port."); } - + return errors; }; - + const findCommonAndCustomPortOverlap = (customPorts: string, commonPorts: string[]): string[] => { const customList = customPorts .split(",") .map((port) => port.trim()) .filter((port) => port !== ""); - + const commonPortNumbers = COMMON_PORTS.filter(({ name }) => commonPorts.includes(name)).map((p) => p.port.toString()); return customList.filter((port) => commonPortNumbers.includes(port)); } @@ -204,33 +204,33 @@ export default function FirewallPage() { }); const validationErrors: string[] = []; - + rules.forEach((rule) => { const descError = validateDescription(rule.description); const sourceIpError = validateSourceIp(rule.sourceIp); const customPortErrors = validateCustomPorts(rule.customPorts, rule.commonPorts); - + if (descError) validationErrors.push(descError); if (sourceIpError) validationErrors.push(sourceIpError); validationErrors.push(...customPortErrors); }); - + setErrors(validationErrors); - + if (validationErrors.length > 0) return; - + try { const { data } = await axios.put( `/api/instances/${instance?.name}/firewall?region=${instance?.region}`, { rules } ); - + setRules(data); } catch (error) { console.error("Error saving rules:", error); } }; - + const handleReset = async () => { setIsLoading(true); try { @@ -427,12 +427,12 @@ export default function FirewallPage() { disabled={errors.length > 0 || formPending()} className="font-heading1 bg-btn1 text-mainbg1 font-semibold px-4 py-2 rounded-sm hover:bg-btnhover1 cursor-pointer flex items-center justify-center hover:shadow-[0_0_10px_#87d9da] transition-all duration-200" > - {formPending() ? - + {formPending() ? + Saving ... - - : "Save"} + + : "Save"}
diff --git a/src/app/instances/[name]/hardware/InstanceTypePage.tsx b/src/app/instances/[name]/hardware/InstanceTypePage.tsx index f3d8ec0..4b0882e 100644 --- a/src/app/instances/[name]/hardware/InstanceTypePage.tsx +++ b/src/app/instances/[name]/hardware/InstanceTypePage.tsx @@ -3,8 +3,8 @@ import { useInstanceContext } from "../InstanceContext"; import { useEffect, useState } from "react"; import axios from "axios"; -import ErrorBanner from "@/app/components/ErrorBanner"; -import SubmissionSpinner from "@/app/components/SubmissionSpinner"; +import ErrorBanner from "@/app/instances/components/ErrorBanner"; +import SubmissionSpinner from "../../components/SubmissionSpinner"; type InstanceTypes = Record; diff --git a/src/app/instances/[name]/hardware/StoragePage.tsx b/src/app/instances/[name]/hardware/StoragePage.tsx index 47012b8..64a1a84 100644 --- a/src/app/instances/[name]/hardware/StoragePage.tsx +++ b/src/app/instances/[name]/hardware/StoragePage.tsx @@ -8,9 +8,9 @@ import { useInstanceContext } from "../InstanceContext"; import axios from "axios"; import { useRouter } from "next/navigation"; import React from "react"; -import { StorageDetails } from "@/app/components/StorageDetails"; -import ErrorBanner from "@/app/components/ErrorBanner"; -import SubmissionSpinner from "@/app/components/SubmissionSpinner"; +import { StorageDetails } from "../../components/StorageDetails"; +import ErrorBanner from "@/app/instances/components/ErrorBanner"; +import SubmissionSpinner from "../../components/SubmissionSpinner"; export function StoragePage() { const router = useRouter(); @@ -166,11 +166,10 @@ export function StoragePage() {
- - - ))} + `} + aria-label="Delete instance" + disabled={ + instance.state === "pending" || + instance.state === "shutting-down" + } + > + + + + + ))} {!isLoading && instances.length === 0 && ( From daa97f1bb8e8d54d80893b91d4d1c03e6500be47 Mon Sep 17 00:00:00 2001 From: Jacqueline Amherst Date: Wed, 16 Apr 2025 14:49:31 -0500 Subject: [PATCH 02/26] move alarms components --- .../{ => instances/[name]/alarms}/components/Dropdown.tsx | 0 .../[name]/alarms}/components/NewAlarmModal.tsx | 0 .../{ => instances/[name]/alarms}/components/SlackModal.tsx | 0 src/app/instances/[name]/alarms/page.tsx | 6 +++--- 4 files changed, 3 insertions(+), 3 deletions(-) rename src/app/{ => instances/[name]/alarms}/components/Dropdown.tsx (100%) rename src/app/{ => instances/[name]/alarms}/components/NewAlarmModal.tsx (100%) rename src/app/{ => instances/[name]/alarms}/components/SlackModal.tsx (100%) diff --git a/src/app/components/Dropdown.tsx b/src/app/instances/[name]/alarms/components/Dropdown.tsx similarity index 100% rename from src/app/components/Dropdown.tsx rename to src/app/instances/[name]/alarms/components/Dropdown.tsx diff --git a/src/app/components/NewAlarmModal.tsx b/src/app/instances/[name]/alarms/components/NewAlarmModal.tsx similarity index 100% rename from src/app/components/NewAlarmModal.tsx rename to src/app/instances/[name]/alarms/components/NewAlarmModal.tsx diff --git a/src/app/components/SlackModal.tsx b/src/app/instances/[name]/alarms/components/SlackModal.tsx similarity index 100% rename from src/app/components/SlackModal.tsx rename to src/app/instances/[name]/alarms/components/SlackModal.tsx diff --git a/src/app/instances/[name]/alarms/page.tsx b/src/app/instances/[name]/alarms/page.tsx index b37a558..941c413 100644 --- a/src/app/instances/[name]/alarms/page.tsx +++ b/src/app/instances/[name]/alarms/page.tsx @@ -2,9 +2,9 @@ import { useInstanceContext } from "../InstanceContext"; import { useEffect, useState } from "react"; -import { NewAlarmModal } from "@/app/components/NewAlarmModal"; -import { SlackModal } from "@/app/components/SlackModal"; -import Dropdown from "@/app/components/Dropdown"; +import { NewAlarmModal } from "./components/NewAlarmModal"; +import { SlackModal } from "./components/SlackModal"; +import Dropdown from "./components/Dropdown"; import axios from "axios"; interface Alarm { From 6b2e555df8eeeda814117115b60a81053a9a6d32 Mon Sep 17 00:00:00 2001 From: Jacqueline Amherst Date: Wed, 16 Apr 2025 19:07:00 -0500 Subject: [PATCH 03/26] add CreateNewInstanceButton --- .../components/CreateNewInstanceButton.tsx | 22 ++++++++++ src/app/instances/page.tsx | 41 ++++++------------- 2 files changed, 35 insertions(+), 28 deletions(-) create mode 100644 src/app/instances/components/CreateNewInstanceButton.tsx diff --git a/src/app/instances/components/CreateNewInstanceButton.tsx b/src/app/instances/components/CreateNewInstanceButton.tsx new file mode 100644 index 0000000..7fe429b --- /dev/null +++ b/src/app/instances/components/CreateNewInstanceButton.tsx @@ -0,0 +1,22 @@ +import Link from "next/link"; + +export default function CreateNewInstanceButton({ isGlowing }: { isGlowing: boolean }) { + return ( +
+

Instances

+ + + +
+ ) +} diff --git a/src/app/instances/page.tsx b/src/app/instances/page.tsx index 9d8de2b..30a5edd 100644 --- a/src/app/instances/page.tsx +++ b/src/app/instances/page.tsx @@ -7,6 +7,7 @@ import { useCallback, useEffect, useState } from "react"; import { useNotificationsContext } from "../NotificationContext"; import SubmissionSpinner from "./components/SubmissionSpinner"; import { StatusLegend } from "../components/statusLegend"; +import CreateNewInstanceButton from "./components/CreateNewInstanceButton"; interface Instance { state: string; @@ -122,23 +123,7 @@ export default function Home() { return (
-
-

Instances

- - - -
- + @@ -205,17 +190,17 @@ export default function Home() { + + + + + + + ) +} diff --git a/src/app/instances/components/InstancesTable.tsx b/src/app/instances/components/InstancesTable.tsx new file mode 100644 index 0000000..791d958 --- /dev/null +++ b/src/app/instances/components/InstancesTable.tsx @@ -0,0 +1,58 @@ +import { StatusLegend } from "@/app/components/statusLegend" +import { InstancesTableProps } from "../types/InstanceTableTypes" +import InstanceRow from "./InstanceRow" + +export default function InstancesTable({ + isLoading, + instances, + openDeleteModal +}: InstancesTableProps) { + return ( +
{instance.state} From 94118f6f4cc5e058eb7ad819a8bf825c7dba52f3 Mon Sep 17 00:00:00 2001 From: Jacqueline Amherst Date: Wed, 16 Apr 2025 19:39:15 -0500 Subject: [PATCH 04/26] extract instance table --- src/app/instances/components/InstanceRow.tsx | 77 +++++++++++ .../instances/components/InstancesTable.tsx | 58 +++++++++ src/app/instances/page.tsx | 123 +----------------- src/app/instances/types/Instance.ts | 7 + src/app/instances/types/InstanceTableTypes.ts | 14 ++ 5 files changed, 163 insertions(+), 116 deletions(-) create mode 100644 src/app/instances/components/InstanceRow.tsx create mode 100644 src/app/instances/components/InstancesTable.tsx create mode 100644 src/app/instances/types/Instance.ts create mode 100644 src/app/instances/types/InstanceTableTypes.ts diff --git a/src/app/instances/components/InstanceRow.tsx b/src/app/instances/components/InstanceRow.tsx new file mode 100644 index 0000000..d4f00d6 --- /dev/null +++ b/src/app/instances/components/InstanceRow.tsx @@ -0,0 +1,77 @@ +import { Trash2 } from "lucide-react"; +import Link from "next/link"; +import { InstanceRowProps } from "../types/InstanceTableTypes"; + +export default function InstanceRow({ + instance, + openDeleteModal +}: InstanceRowProps) { + return ( +
+ {instance.state === "pending" || + instance.state === "shutting-down" || + instance.state === "terminated" ? ( + + {instance.name} + + ) : ( + + {instance.name} + + )} + + {instance.id} + + {instance.region} + + {instance.state} + + +
+ + + + + + + + + + + + {isLoading + ? Array.from({ length: 3 }).map((_, idx) => ( + + + + + + + + )) + : instances.map((instance) => ( + + ))} + +
NameInstance IDData Center + Status +
+
+
+
+
+
+
+
+
+
+
+ ) +} diff --git a/src/app/instances/page.tsx b/src/app/instances/page.tsx index 30a5edd..8e4c836 100644 --- a/src/app/instances/page.tsx +++ b/src/app/instances/page.tsx @@ -1,20 +1,12 @@ "use client"; -import Link from "next/link"; import axios from "axios"; -import { Trash2 } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { useNotificationsContext } from "../NotificationContext"; import SubmissionSpinner from "./components/SubmissionSpinner"; -import { StatusLegend } from "../components/statusLegend"; import CreateNewInstanceButton from "./components/CreateNewInstanceButton"; - -interface Instance { - state: string; - name: string; - id: string; - region: string; -} +import InstancesTable from "./components/InstancesTable"; +import { Instance } from "./types/Instance"; export default function Home() { const [instances, setInstances] = useState([]); @@ -124,113 +116,12 @@ export default function Home() { return (
- - - - - - - - - - + - - {isLoading - ? Array.from({ length: 3 }).map((_, idx) => ( - - - - - - - - )) - : instances.map((instance) => ( - - - - - - - - ))} - -
NameInstance IDData Center - Status -
-
-
-
-
-
-
-
-
-
-
- {instance.state === "pending" || - instance.state === "shutting-down" || - instance.state === "terminated" ? ( - - {instance.name} - - ) : ( - - {instance.name} - - )} - - {instance.id} - - {instance.region} - - {instance.state} - - -
{!isLoading && instances.length === 0 && (

No instances yet. Let’s spin one up! diff --git a/src/app/instances/types/Instance.ts b/src/app/instances/types/Instance.ts new file mode 100644 index 0000000..3fd5e30 --- /dev/null +++ b/src/app/instances/types/Instance.ts @@ -0,0 +1,7 @@ +export interface Instance { + state: string; + name: string; + id: string; + region: string; +} + diff --git a/src/app/instances/types/InstanceTableTypes.ts b/src/app/instances/types/InstanceTableTypes.ts new file mode 100644 index 0000000..91e6982 --- /dev/null +++ b/src/app/instances/types/InstanceTableTypes.ts @@ -0,0 +1,14 @@ +import { Instance } from "./Instance"; + +export interface InstancesTableProps { + isLoading: boolean; + instances: Instance[]; + openDeleteModal: (instance: Instance) => void; +} + +export interface InstanceRowProps { + instance: Instance; + openDeleteModal: (instance: Instance) => void; +} + + From 9343b8cafe27ade1f69d5eddcfb438d7ca8c12c4 Mon Sep 17 00:00:00 2001 From: Jacqueline Amherst Date: Wed, 16 Apr 2025 20:02:37 -0500 Subject: [PATCH 05/26] change font loading from swap to optional to appease Chrome --- src/app/layout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ce35648..ceeec7c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -13,14 +13,14 @@ interface RootLayoutProps { const montserrat = Montserrat({ weight: ["400", "600", "700"], subsets: ["latin"], - display: "swap", + display: "optional", variable: "--font-montserrat", }); const metrophobic = Metrophobic({ weight: ["400"], subsets: ["latin"], - display: "swap", + display: "optional", variable: "--font-metrophobic", }); From 88edb001bfd24f84e418cf96c58d721e470ca4e4 Mon Sep 17 00:00:00 2001 From: Jacqueline Amherst Date: Wed, 16 Apr 2025 20:10:20 -0500 Subject: [PATCH 06/26] change type file names to match convention --- src/app/instances/components/InstanceRow.tsx | 2 +- src/app/instances/components/InstancesTable.tsx | 2 +- src/app/instances/page.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/instances/components/InstanceRow.tsx b/src/app/instances/components/InstanceRow.tsx index d4f00d6..8b3e064 100644 --- a/src/app/instances/components/InstanceRow.tsx +++ b/src/app/instances/components/InstanceRow.tsx @@ -1,6 +1,6 @@ import { Trash2 } from "lucide-react"; import Link from "next/link"; -import { InstanceRowProps } from "../types/InstanceTableTypes"; +import { InstanceRowProps } from "../types/instanceTableTypes"; export default function InstanceRow({ instance, diff --git a/src/app/instances/components/InstancesTable.tsx b/src/app/instances/components/InstancesTable.tsx index 791d958..efbb2e1 100644 --- a/src/app/instances/components/InstancesTable.tsx +++ b/src/app/instances/components/InstancesTable.tsx @@ -1,5 +1,5 @@ import { StatusLegend } from "@/app/components/statusLegend" -import { InstancesTableProps } from "../types/InstanceTableTypes" +import { InstancesTableProps } from "../types/instanceTableTypes" import InstanceRow from "./InstanceRow" export default function InstancesTable({ diff --git a/src/app/instances/page.tsx b/src/app/instances/page.tsx index 8e4c836..12edf07 100644 --- a/src/app/instances/page.tsx +++ b/src/app/instances/page.tsx @@ -6,7 +6,7 @@ import { useNotificationsContext } from "../NotificationContext"; import SubmissionSpinner from "./components/SubmissionSpinner"; import CreateNewInstanceButton from "./components/CreateNewInstanceButton"; import InstancesTable from "./components/InstancesTable"; -import { Instance } from "./types/Instance"; +import { Instance } from "./types/instance"; export default function Home() { const [instances, setInstances] = useState([]); From 9c83fd10bcaa14a0d4d5f396a80c1835c1af7405 Mon Sep 17 00:00:00 2001 From: Jacqueline Amherst Date: Wed, 16 Apr 2025 20:50:25 -0500 Subject: [PATCH 07/26] extract delete instance modal --- .../components/DeleteInstanceModal.tsx | 65 +++++++++++++++++++ src/app/instances/components/InstanceRow.tsx | 8 ++- .../instances/components/InstancesTable.tsx | 9 ++- src/app/instances/page.tsx | 48 +------------- src/app/instances/types/InstanceTableTypes.ts | 14 ---- 5 files changed, 82 insertions(+), 62 deletions(-) create mode 100644 src/app/instances/components/DeleteInstanceModal.tsx delete mode 100644 src/app/instances/types/InstanceTableTypes.ts diff --git a/src/app/instances/components/DeleteInstanceModal.tsx b/src/app/instances/components/DeleteInstanceModal.tsx new file mode 100644 index 0000000..60fda13 --- /dev/null +++ b/src/app/instances/components/DeleteInstanceModal.tsx @@ -0,0 +1,65 @@ +import { useState } from "react"; +import SubmissionSpinner from "../components/SubmissionSpinner"; +import { Instance } from "../types/instance"; + +interface DeleteInstanceModalProps { + selectedInstance: Instance; + onClose: () => void; + handleDelete: () => void; + isDeleting: boolean; +} + +export default function DeleteInstanceModal({ + selectedInstance, + onClose, + handleDelete, + isDeleting, +}: DeleteInstanceModalProps) { + const [inputText, setInputText] = useState(""); + + return ( +

+
+

+ Delete {selectedInstance.name}? +

+

+ Deleting this instance is permanent and will result in the loss of + all data stored on it. This action cannot be undone. +

+

+ Type {selectedInstance.name} to confirm deletion. +

+ setInputText(e.target.value)} + /> +
+ + +
+
+
+ + ) +} diff --git a/src/app/instances/components/InstanceRow.tsx b/src/app/instances/components/InstanceRow.tsx index 8b3e064..c7a6cd0 100644 --- a/src/app/instances/components/InstanceRow.tsx +++ b/src/app/instances/components/InstanceRow.tsx @@ -1,6 +1,12 @@ import { Trash2 } from "lucide-react"; import Link from "next/link"; -import { InstanceRowProps } from "../types/instanceTableTypes"; +import { Instance } from "../types/instance"; + +interface InstanceRowProps { + instance: Instance; + openDeleteModal: (instance: Instance) => void; +} + export default function InstanceRow({ instance, diff --git a/src/app/instances/components/InstancesTable.tsx b/src/app/instances/components/InstancesTable.tsx index efbb2e1..fd6156f 100644 --- a/src/app/instances/components/InstancesTable.tsx +++ b/src/app/instances/components/InstancesTable.tsx @@ -1,6 +1,13 @@ import { StatusLegend } from "@/app/components/statusLegend" -import { InstancesTableProps } from "../types/instanceTableTypes" import InstanceRow from "./InstanceRow" +import { Instance } from "../types/instance"; + +interface InstancesTableProps { + isLoading: boolean; + instances: Instance[]; + openDeleteModal: (instance: Instance) => void; +} + export default function InstancesTable({ isLoading, diff --git a/src/app/instances/page.tsx b/src/app/instances/page.tsx index 12edf07..790eb0f 100644 --- a/src/app/instances/page.tsx +++ b/src/app/instances/page.tsx @@ -3,7 +3,7 @@ import axios from "axios"; import { useCallback, useEffect, useState } from "react"; import { useNotificationsContext } from "../NotificationContext"; -import SubmissionSpinner from "./components/SubmissionSpinner"; +import DeleteInstanceModal from "./components/DeleteInstanceModal"; import CreateNewInstanceButton from "./components/CreateNewInstanceButton"; import InstancesTable from "./components/InstancesTable"; import { Instance } from "./types/instance"; @@ -15,7 +15,6 @@ export default function Home() { const [selectedInstance, setSelectedInstance] = useState( null ); - const [inputText, setInputText] = useState(""); const [isDeleting, setIsDeleting] = useState(false); const { notifications, @@ -72,14 +71,12 @@ export default function Home() { const openDeleteModal = (instance: Instance) => { setSelectedInstance(instance); - setInputText(""); setShowModal(true); }; const closeDeleteModal = () => { setShowModal(false); setSelectedInstance(null); - setInputText(""); }; const handleDelete = async () => { @@ -129,48 +126,7 @@ export default function Home() { )} {showModal && selectedInstance && ( -
-
-

- Delete {selectedInstance.name}? -

-

- Deleting this instance is permanent and will result in the loss of - all data stored on it. This action cannot be undone. -

-

- Type {selectedInstance.name} to confirm deletion. -

- setInputText(e.target.value)} - /> -
- - -
-
-
+ )}