Merge pull request #2534 from bluewave-labs/issue-2150-move-status-page-delete-button

Move Status Page Delete Button
This commit is contained in:
Alexander Holliday
2025-07-04 02:54:02 +08:00
committed by GitHub
6 changed files with 111 additions and 74 deletions

View File

@@ -1,6 +1,6 @@
// Components
import { TabContext } from "@mui/lab";
import { Tab, useTheme } from "@mui/material";
import { Tab } from "@mui/material";
import Settings from "./Settings";
import Content from "./Content";
@@ -22,8 +22,12 @@ const Tabs = ({
tab,
setTab,
TAB_LIST,
handleDelete,
isDeleteOpen,
setIsDeleteOpen,
isDeleting,
isLoading,
}) => {
const theme = useTheme();
return (
<TabContext value={TAB_LIST[tab]}>
<CustomTabList
@@ -32,7 +36,7 @@ const Tabs = ({
}}
aria-label="status page tabs"
>
{TAB_LIST.map((tabLabel, idx) => (
{TAB_LIST.map((tabLabel) => (
<Tab
key={tabLabel}
label={tabLabel}
@@ -50,6 +54,11 @@ const Tabs = ({
removeLogo={removeLogo}
errors={errors}
isCreate={isCreate}
handleDelete={handleDelete}
isDeleteOpen={isDeleteOpen}
setIsDeleteOpen={setIsDeleteOpen}
isDeleting={isDeleting}
isLoading={isLoading}
/>
) : (
<Content
@@ -67,6 +76,7 @@ const Tabs = ({
};
Tabs.propTypes = {
isCreate: PropTypes.bool,
form: PropTypes.object,
errors: PropTypes.object,
monitors: PropTypes.array,
@@ -79,6 +89,11 @@ Tabs.propTypes = {
tab: PropTypes.number,
setTab: PropTypes.func,
TAB_LIST: PropTypes.array,
handleDelete: PropTypes.func,
isDeleteOpen: PropTypes.bool,
setIsDeleteOpen: PropTypes.func,
isDeleting: PropTypes.bool,
isLoading: PropTypes.bool,
};
export default Tabs;

View File

@@ -3,6 +3,8 @@ import { Stack, Button, Typography } from "@mui/material";
import Tabs from "./Components/Tabs";
import GenericFallback from "../../../Components/GenericFallback";
import SkeletonLayout from "./Components/Skeleton";
import Dialog from "../../../Components/Dialog";
import Breadcrumbs from "../../../Components/Breadcrumbs";
//Utils
import { useTheme } from "@emotion/react";
import { useState, useEffect, useRef, useCallback } from "react";
@@ -15,9 +17,8 @@ import { useNavigate } from "react-router-dom";
import { useStatusPageFetch } from "../Status/Hooks/useStatusPageFetch";
import { useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useStatusPageDelete } from "../Status/Hooks/useStatusPageDelete";
//Constants
const TAB_LIST = ["General settings", "Contents"];
const ERROR_TAB_MAPPING = [
["companyName", "url", "timezone", "color", "isPublished", "logo"],
["monitors", "showUptimePercentage", "showCharts", "showAdminLoginLink"],
@@ -28,6 +29,7 @@ const CreateStatusPage = () => {
//Local state
const [tab, setTab] = useState(0);
const [progress, setProgress] = useState({ value: 0, isLoading: false });
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
const [form, setForm] = useState({
isPublished: false,
companyName: "",
@@ -52,13 +54,13 @@ const CreateStatusPage = () => {
//Utils
const theme = useTheme();
const [monitors, isLoading, networkError] = useMonitorsFetch();
const [createStatusPage, createStatusIsLoading, createStatusPageNetworkError] =
useCreateStatusPage(isCreate);
const [createStatusPage] = useCreateStatusPage(isCreate);
const navigate = useNavigate();
const { t } = useTranslation();
const [statusPage, statusPageMonitors, statusPageIsLoading, statusPageNetworkError] =
const [statusPage, statusPageMonitors, statusPageIsLoading, , fetchStatusPage] =
useStatusPageFetch(isCreate, url);
const [deleteStatusPage, isDeleting] = useStatusPageDelete(fetchStatusPage, url);
console.log(JSON.stringify(form, null, 2));
// Handlers
@@ -124,6 +126,19 @@ const CreateStatusPage = () => {
setProgress({ value: 0, isLoading: false });
};
/**
* Handle status page deletion with optimistic UI update
* Immediately navigates away without waiting for the deletion to complete
* to prevent unnecessary network requests for the deleted page
*/
const handleDelete = async () => {
setIsDeleteOpen(false);
// Start deletion process but don't wait for it
deleteStatusPage();
// Immediately navigate away to prevent additional fetches for the deleted page
navigate("/status");
};
const handleSubmit = async () => {
let toSubmit = {
...form,
@@ -137,9 +152,7 @@ const CreateStatusPage = () => {
const success = await createStatusPage({ form });
if (success) {
createToast({
body: isCreate
? "Status page created successfully"
: "Status page updated successfully",
body: isCreate ? t("statusPage.createSuccess") : t("statusPage.updateSuccess"),
});
navigate(`/status/uptime/${form.url}`);
}
@@ -162,7 +175,7 @@ const CreateStatusPage = () => {
// If we get -1, there's an unknown error
if (errorTabs[0] === -1) {
createToast({ body: "Unknown error" });
createToast({ body: t("common.toasts.unknownError") });
return;
}
@@ -223,6 +236,37 @@ const CreateStatusPage = () => {
// Load fields
return (
<Stack gap={theme.spacing(10)}>
<Breadcrumbs
list={[
{ name: t("statusBreadCrumbsStatusPages", "Status"), path: "/status" },
{ name: t("statusBreadCrumbsDetails", "Details"), path: `/status/${url}` },
{ name: t("configure", "Configure"), path: `/status/create/${url}` },
]}
/>
{!isCreate && (
<Stack
direction="row"
justifyContent="flex-end"
>
<Button
loading={isDeleting}
variant="contained"
color="error"
onClick={() => setIsDeleteOpen(true)}
>
{t("remove")}
</Button>
<Dialog
title={t("deleteStatusPage")}
onConfirm={handleDelete}
onCancel={() => setIsDeleteOpen(false)}
open={isDeleteOpen}
confirmationButtonLabel={t("deleteStatusPageConfirm")}
description={t("deleteStatusPageDescription")}
isLoading={isDeleting || statusPageIsLoading}
/>
</Stack>
)}
<Tabs
form={form}
errors={errors}
@@ -235,8 +279,16 @@ const CreateStatusPage = () => {
removeLogo={removeLogo}
tab={tab}
setTab={setTab}
TAB_LIST={TAB_LIST}
TAB_LIST={[
t("statusPage.generalSettings", "General settings"),
t("statusPage.contents", "Contents"),
]}
isCreate={isCreate}
handleDelete={handleDelete}
isDeleteOpen={isDeleteOpen}
setIsDeleteOpen={setIsDeleteOpen}
isDeleting={isDeleting}
isLoading={statusPageIsLoading}
/>
<Stack
direction="row"

View File

@@ -11,7 +11,7 @@ import { useLocation } from "react-router-dom";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting, url, type }) => {
const Controls = ({ url, type }) => {
const theme = useTheme();
const { t } = useTranslation();
const location = useLocation();
@@ -27,16 +27,6 @@ const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting, url, type }) => {
direction="row"
gap={theme.spacing(2)}
>
<Box>
<Button
variant="contained"
color="error"
onClick={() => setIsDeleteOpen(!isDeleteOpen)}
loading={isDeleting}
>
{t("delete")}
</Button>
</Box>
<Box>
<Button
variant="contained"
@@ -65,21 +55,10 @@ const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting, url, type }) => {
Controls.propTypes = {
type: PropTypes.string,
isDeleting: PropTypes.bool,
url: PropTypes.string,
isDeleteOpen: PropTypes.bool.isRequired,
setIsDeleteOpen: PropTypes.func.isRequired,
};
const ControlsHeader = ({
statusPage,
isPublic,
isDeleting,
isDeleteOpen,
setIsDeleteOpen,
url,
type = "uptime",
}) => {
const ControlsHeader = ({ statusPage, isPublic, url, type = "uptime" }) => {
const theme = useTheme();
const { t } = useTranslation();
const publicUrl = `/status/uptime/public/${url}`;
@@ -137,9 +116,6 @@ const ControlsHeader = ({
)}
</Stack>
<Controls
isDeleting={isDeleting}
isDeleteOpen={isDeleteOpen}
setIsDeleteOpen={setIsDeleteOpen}
url={url}
type={type}
/>
@@ -152,9 +128,6 @@ ControlsHeader.propTypes = {
url: PropTypes.string,
statusPage: PropTypes.object,
isPublic: PropTypes.bool,
isDeleting: PropTypes.bool,
isDeleteOpen: PropTypes.bool.isRequired,
setIsDeleteOpen: PropTypes.func.isRequired,
type: PropTypes.string,
};

View File

@@ -1,19 +1,35 @@
import { useSelector } from "react-redux";
import { useState } from "react";
import { networkService } from "../../../../main";
import { networkService } from "../../../../Utils/NetworkService";
import { createToast } from "../../../../Utils/toastUtils";
import { useTranslation } from "react-i18next";
/**
* Hook for deleting a status page with optimistic UI update
* @param {Function} fetchStatusPage - Function to fetch status page data
* @param {string} url - URL of the status page
* @returns {Array} - [deleteStatusPage function, isLoading state]
*/
const useStatusPageDelete = (fetchStatusPage, url) => {
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
/**
* Delete a status page with optimistic UI update
* @returns {Promise<boolean>} - Success status
*/
const deleteStatusPage = async () => {
// We don't need to call fetchStatusPage after deletion
// This prevents the 404 error when trying to fetch a deleted status page
try {
setIsLoading(true);
await networkService.deleteStatusPage({ url });
fetchStatusPage?.();
createToast({
body: t("statusPage.deleteSuccess", "Status page deleted successfully"),
});
return true;
} catch (error) {
createToast({
body: error.message,
body: t("statusPage.deleteFailed", "Failed to delete status page"),
});
return false;
} finally {

View File

@@ -6,7 +6,6 @@ import ControlsHeader from "./Components/ControlsHeader";
import SkeletonLayout from "./Components/Skeleton";
import StatusBar from "./Components/StatusBar";
import MonitorsList from "./Components/MonitorsList";
import Dialog from "../../../Components/Dialog";
import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx";
import TextLink from "../../../Components/TextLink";
@@ -15,27 +14,19 @@ import { useStatusPageFetch } from "./Hooks/useStatusPageFetch";
import { useTheme } from "@emotion/react";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import { useLocation } from "react-router-dom";
import { useStatusPageDelete } from "./Hooks/useStatusPageDelete";
import { useState } from "react";
import { useParams } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
const PublicStatus = () => {
const { url } = useParams();
// Local state
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
// Utils
const theme = useTheme();
const { t } = useTranslation();
const location = useLocation();
const navigate = useNavigate();
const isAdmin = useIsAdmin();
const [statusPage, monitors, isLoading, networkError, fetchStatusPage] =
useStatusPageFetch(false, url);
const [deleteStatusPage, isDeleting] = useStatusPageDelete(fetchStatusPage, url);
const [statusPage, monitors, isLoading, networkError] = useStatusPageFetch(false, url);
// Breadcrumbs
const crumbs = [
@@ -158,9 +149,6 @@ const PublicStatus = () => {
{!isPublic && <Breadcrumbs list={crumbs} />}
<ControlsHeader
statusPage={statusPage}
isDeleting={isDeleting}
isDeleteOpen={isDeleteOpen}
setIsDeleteOpen={setIsDeleteOpen}
url={url}
isPublic={isPublic}
/>
@@ -168,21 +156,6 @@ const PublicStatus = () => {
<StatusBar monitors={monitors} />
<MonitorsList monitors={monitors} />
{link}
<Dialog
title={t("deleteStatusPage")}
onConfirm={() => {
deleteStatusPage();
setIsDeleteOpen(false);
navigate("/status");
}}
onCancel={() => {
setIsDeleteOpen(false);
}}
open={isDeleteOpen}
confirmationButtonLabel={t("deleteStatusPageConfirm")}
description={t("deleteStatusPageDescription")}
isLoading={isDeleting || isLoading}
/>
</Stack>
);
};

View File

@@ -802,6 +802,14 @@
"statusPageStatusNoPage": "There's no status page here.",
"statusPageStatusNotPublic": "This status page is not public.",
"statusPageStatusServiceStatus": "Service status",
"statusPage": {
"deleteSuccess": "Status page deleted successfully",
"deleteFailed": "Failed to delete status page",
"createSuccess": "Status page created successfully",
"updateSuccess": "Status page updated successfully",
"generalSettings": "General settings",
"contents": "Contents"
},
"submit": "Submit",
"teamPanel": {
"cancel": "Cancel",