diff --git a/web/apps/labelstudio/src/components/Modal/Modal.scss b/web/apps/labelstudio/src/components/Modal/Modal.scss
index 4e7a43df7127..8a3827f7c3d0 100644
--- a/web/apps/labelstudio/src/components/Modal/Modal.scss
+++ b/web/apps/labelstudio/src/components/Modal/Modal.scss
@@ -76,7 +76,7 @@
}
&__footer {
- padding: 1rem 1.5rem;
+ padding: var(--spacing-base) var(--spacing-wider);
text-align: center;
font-size: 14px;
line-height: 22px;
diff --git a/web/apps/labelstudio/src/pages/Settings/DangerZone.jsx b/web/apps/labelstudio/src/pages/Settings/DangerZone.jsx
index 578e890b4808..1dbc446bdeff 100644
--- a/web/apps/labelstudio/src/pages/Settings/DangerZone.jsx
+++ b/web/apps/labelstudio/src/pages/Settings/DangerZone.jsx
@@ -1,9 +1,12 @@
import { useMemo, useState } from "react";
import { useHistory } from "react-router";
-import { Button } from "@humansignal/ui";
+import { Button, Typography, useToast } from "@humansignal/ui";
import { useUpdatePageTitle, createTitleFromSegments } from "@humansignal/core";
import { Label } from "../../components/Form";
-import { confirm } from "../../components/Modal/Modal";
+import { modal } from "../../components/Modal/Modal";
+import { useModalControls } from "../../components/Modal/ModalPopup";
+import Input from "../../components/Form/Elements/Input/Input";
+import { Space } from "../../components/Space/Space";
import { Spinner } from "../../components/Spinner/Spinner";
import { useAPI } from "../../providers/ApiProvider";
import { useProject } from "../../providers/ProjectProvider";
@@ -13,45 +16,143 @@ export const DangerZone = () => {
const { project } = useProject();
const api = useAPI();
const history = useHistory();
+ const toast = useToast();
const [processing, setProcessing] = useState(null);
useUpdatePageTitle(createTitleFromSegments([project?.title, "Danger Zone"]));
+ const showDangerConfirmation = ({ title, message, requiredWord, buttonText, onConfirm }) => {
+ const isDev = process.env.NODE_ENV === "development";
+
+ return modal({
+ title,
+ width: 600,
+ allowClose: false,
+ body: () => {
+ const ctrl = useModalControls();
+ const inputValue = ctrl?.state?.inputValue || "";
+
+ return (
+
+
+ {message}
+
+ ctrl?.setState({ inputValue: e.target.value })}
+ autoFocus
+ data-testid="danger-zone-confirmation-input"
+ autoComplete="off"
+ />
+
+ );
+ },
+ footer: () => {
+ const ctrl = useModalControls();
+ const inputValue = (ctrl?.state?.inputValue || "").trim().toLowerCase();
+ const isValid = isDev || inputValue === requiredWord.toLowerCase();
+
+ return (
+
+ ctrl?.hide()}
+ data-testid="danger-zone-cancel-button"
+ >
+ Cancel
+
+ {
+ await onConfirm();
+ ctrl?.hide();
+ }}
+ data-testid="danger-zone-confirm-button"
+ >
+ {buttonText}
+
+
+ );
+ },
+ });
+ };
+
const handleOnClick = (type) => () => {
- confirm({
- title: "Action confirmation",
- body: "You're about to delete all things. This action cannot be undone.",
- okText: "Proceed",
- buttonLook: "negative",
- onOk: async () => {
+ const actionConfig = {
+ reset_cache: {
+ title: "Reset Cache",
+ message: (
+ <>
+ You are about to reset the cache for {project.title} . This action cannot be undone.
+ >
+ ),
+ requiredWord: "cache",
+ buttonText: "Reset Cache",
+ },
+ tabs: {
+ title: "Drop All Tabs",
+ message: (
+ <>
+ You are about to drop all tabs for {project.title} . This action cannot be undone.
+ >
+ ),
+ requiredWord: "tabs",
+ buttonText: "Drop All Tabs",
+ },
+ project: {
+ title: "Delete Project",
+ message: (
+ <>
+ You are about to delete the project {project.title} . This action cannot be undone.
+ >
+ ),
+ requiredWord: "delete",
+ buttonText: "Delete Project",
+ },
+ };
+
+ const config = actionConfig[type];
+
+ if (!config) {
+ return;
+ }
+
+ showDangerConfirmation({
+ ...config,
+ onConfirm: async () => {
setProcessing(type);
- if (type === "annotations") {
- // console.log('delete annotations');
- } else if (type === "tasks") {
- // console.log('delete tasks');
- } else if (type === "predictions") {
- // console.log('delete predictions');
- } else if (type === "reset_cache") {
- await api.callApi("projectResetCache", {
- params: {
- pk: project.id,
- },
- });
- } else if (type === "tabs") {
- await api.callApi("deleteTabs", {
- body: {
- project: project.id,
- },
- });
- } else if (type === "project") {
- await api.callApi("deleteProject", {
- params: {
- pk: project.id,
- },
- });
- history.replace("/projects");
+ try {
+ if (type === "reset_cache") {
+ await api.callApi("projectResetCache", {
+ params: {
+ pk: project.id,
+ },
+ });
+ toast.show({ message: "Cache reset successfully" });
+ } else if (type === "tabs") {
+ await api.callApi("deleteTabs", {
+ body: {
+ project: project.id,
+ },
+ });
+ toast.show({ message: "All tabs dropped successfully" });
+ } else if (type === "project") {
+ await api.callApi("deleteProject", {
+ params: {
+ pk: project.id,
+ },
+ });
+ toast.show({ message: "Project deleted successfully" });
+ history.replace("/projects");
+ }
+ } catch (error) {
+ toast.show({ message: `Error: ${error.message}`, type: "error" });
+ } finally {
+ setProcessing(null);
}
- setProcessing(null);
},
});
};
@@ -97,8 +198,13 @@ export const DangerZone = () => {
return (
-
Danger Zone
-
+
+ Danger Zone
+
+
+ Perform these actions at your own risk. Actions you take on this page can't be reverted. Make sure your data is
+ backed up.
+
{project.id ? (
@@ -109,7 +215,9 @@ export const DangerZone = () => {
return (
btn.disabled !== true && (
-
{btn.label}
+
+ {btn.label}
+
{btn.help && }