fix: code splitting for workspace delete modal (#6581)

* fix: code splitting for delete modal

* fix: redirected to profile post deletion

* fix: translations
This commit is contained in:
Akshita Goyal 2025-02-12 17:15:40 +05:30 committed by GitHub
parent cc9b448a9b
commit 6a3ccafe35
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 264 additions and 221 deletions

View file

@ -1253,9 +1253,19 @@
"company_size": "Company size", "company_size": "Company size",
"url": "Workspace URL", "url": "Workspace URL",
"update_workspace": "Update workspace", "update_workspace": "Update workspace",
"delete_workspace": "Delete workspace", "delete_workspace": "Delete this workspace",
"delete_workspace_description": "When deleting a workspace, all of the data and resources within that workspace will be permanently removed and cannot be recovered.", "delete_workspace_description": "When deleting a workspace, all of the data and resources within that workspace will be permanently removed and cannot be recovered.",
"delete_btn": "Delete my workspace", "delete_btn": "Delete this workspace",
"delete_modal": {
"title": "Are you sure you want to delete this workspace?",
"description": "You have an active trial to one of our paid plans. Please cancel it first to proceed.",
"dismiss": "Dismiss",
"cancel": "Cancel trial",
"success_title": "Workspace deleted.",
"success_message": "You will soon go to your profile page.",
"error_title": "That didn't work.",
"error_message": "Try again, please."
},
"errors": { "errors": {
"name": { "name": {
"required": "Name is required", "required": "Name is required",

View file

@ -1422,9 +1422,19 @@
"company_size": "Tamaño de la empresa", "company_size": "Tamaño de la empresa",
"url": "URL del espacio de trabajo", "url": "URL del espacio de trabajo",
"update_workspace": "Actualizar espacio de trabajo", "update_workspace": "Actualizar espacio de trabajo",
"delete_workspace": "Eliminar espacio de trabajo", "delete_workspace": "Eliminar este espacio de trabajo",
"delete_workspace_description": "Al eliminar un espacio de trabajo, todos los datos y recursos dentro de ese espacio de trabajo se eliminarán permanentemente y no se podrán recuperar.", "delete_workspace_description": "Al eliminar un espacio de trabajo, todos los datos y recursos dentro de ese espacio se eliminarán permanentemente y no podrán recuperarse.",
"delete_btn": "Eliminar mi espacio de trabajo", "delete_btn": "Eliminar este espacio de trabajo",
"delete_modal": {
"title": "¿Está seguro de que desea eliminar este espacio de trabajo?",
"description": "Tiene una prueba activa de uno de nuestros planes de pago. Por favor, cancelela primero para continuar.",
"dismiss": "Descartar",
"cancel": "Cancelar prueba",
"success_title": "Espacio de trabajo eliminado.",
"success_message": "Pronto irá a su página de perfil.",
"error_title": "Eso no funcionó.",
"error_message": "Por favor, inténtelo de nuevo."
},
"errors": { "errors": {
"name": { "name": {
"required": "El nombre es obligatorio", "required": "El nombre es obligatorio",

View file

@ -1422,9 +1422,19 @@
"company_size": "Taille de l'entreprise", "company_size": "Taille de l'entreprise",
"url": "URL de l'espace de travail", "url": "URL de l'espace de travail",
"update_workspace": "Mettre à jour l'espace de travail", "update_workspace": "Mettre à jour l'espace de travail",
"delete_workspace": "Supprimer l'espace de travail", "delete_workspace": "Supprimer cet espace de travail",
"delete_workspace_description": "Lors de la suppression d'un espace de travail, toutes les données et ressources au sein de cet espace de travail seront définitivement supprimées et ne pourront pas être récupérées.", "delete_workspace_description": "Lors de la suppression d'un espace de travail, toutes les données et ressources au sein de cet espace seront définitivement supprimées et ne pourront pas être récupérées.",
"delete_btn": "Supprimer mon espace de travail", "delete_btn": "Supprimer cet espace de travail",
"delete_modal": {
"title": "Êtes-vous sûr de vouloir supprimer cet espace de travail ?",
"description": "Vous avez un essai actif sur l'un de nos forfaits payants. Veuillez d'abord l'annuler pour continuer.",
"dismiss": "Fermer",
"cancel": "Annuler l'essai",
"success_title": "Espace de travail supprimé.",
"success_message": "Vous serez bientôt redirigé vers votre page de profil.",
"error_title": "Cela n'a pas fonctionné.",
"error_message": "Veuillez réessayer."
},
"errors": { "errors": {
"name": { "name": {
"required": "Le nom est requis", "required": "Le nom est requis",

View file

@ -1422,9 +1422,19 @@
"company_size": "会社の規模", "company_size": "会社の規模",
"url": "ワークスペースURL", "url": "ワークスペースURL",
"update_workspace": "ワークスペースを更新", "update_workspace": "ワークスペースを更新",
"delete_workspace": "ワークスペースを削除", "delete_workspace": "このワークスペースを削除",
"delete_workspace_description": "ワークスペースを削除すると、そのワークスペース内のすべてのデータとリソースが永久に削除され、復元できなくなります。", "delete_workspace_description": "ワークスペースを削除すると、そのワークスペース内のすべてのデータとリソースが完全に削除され、復元することはできません。",
"delete_btn": "ワークスペースを削除", "delete_btn": "このワークスペースを削除",
"delete_modal": {
"title": "このワークスペースを削除してもよろしいですか?",
"description": "有料プランの無料トライアルが有効です。続行するには、まずトライアルをキャンセルしてください。",
"dismiss": "閉じる",
"cancel": "トライアルをキャンセル",
"success_title": "ワークスペースが削除されました。",
"success_message": "まもなくプロフィールページに移動します。",
"error_title": "操作に失敗しました。",
"error_message": "もう一度お試しください。"
},
"errors": { "errors": {
"name": { "name": {
"required": "名前は必須です", "required": "名前は必須です",

View file

@ -1422,9 +1422,19 @@
"company_size": "公司规模", "company_size": "公司规模",
"url": "工作区网址", "url": "工作区网址",
"update_workspace": "更新工作区", "update_workspace": "更新工作区",
"delete_workspace": "删除工作区", "delete_workspace": "删除此工作区",
"delete_workspace_description": "删除工作区时,该工作区内的所有数据和资源将被永久删除且无法恢复。", "delete_workspace_description": "删除工作区时,该工作区内的所有数据和资源将被永久删除,且无法恢复。",
"delete_btn": "删除我的工作区", "delete_btn": "删除此工作区",
"delete_modal": {
"title": "确定要删除此工作区吗?",
"description": "您目前正在试用我们的付费方案。请先取消试用后再继续。",
"dismiss": "关闭",
"cancel": "取消试用",
"success_title": "工作区已删除。",
"success_message": "即将跳转到您的个人资料页面。",
"error_title": "操作失败。",
"error_message": "请重试。"
},
"errors": { "errors": {
"name": { "name": {
"required": "名称为必填项", "required": "名称为必填项",

View file

@ -0,0 +1,27 @@
"use client";
import React from "react";
import { observer } from "mobx-react";
import type { IWorkspace } from "@plane/types";
// ui
import { EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
// constants
// hooks
import { DeleteWorkspaceForm } from "@/components/workspace/delete-workspace-form";
type Props = {
isOpen: boolean;
data: IWorkspace | null;
onClose: () => void;
};
export const DeleteWorkspaceModal: React.FC<Props> = observer((props) => {
const { isOpen, data, onClose } = props;
return (
<ModalCore isOpen={isOpen} handleClose={() => onClose()} position={EModalPosition.CENTER} width={EModalWidth.XL}>
<DeleteWorkspaceForm data={data} onClose={onClose} />
</ModalCore>
);
});

View file

@ -6,8 +6,8 @@ import { useTranslation } from "@plane/i18n";
import { IWorkspace } from "@plane/types"; import { IWorkspace } from "@plane/types";
// ui // ui
import { Button, Collapsible } from "@plane/ui"; import { Button, Collapsible } from "@plane/ui";
import { DeleteWorkspaceModal } from "./delete-workspace-modal";
// components // components
import { DeleteWorkspaceModal } from "@/components/workspace";
type TDeleteWorkspace = { type TDeleteWorkspace = {
workspace: IWorkspace | null; workspace: IWorkspace | null;

View file

@ -0,0 +1,171 @@
"use client";
import React from "react";
import { observer } from "mobx-react";
import { Controller, useForm } from "react-hook-form";
import { AlertTriangle } from "lucide-react";
// types
import { WORKSPACE_DELETED } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import type { IWorkspace } from "@plane/types";
// ui
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// constants
// hooks
import { cn } from "@plane/utils";
import { useEventTracker, useWorkspace } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
type Props = {
data: IWorkspace | null;
onClose: () => void;
};
const defaultValues = {
workspaceName: "",
confirmDelete: "",
};
export const DeleteWorkspaceForm: React.FC<Props> = observer((props) => {
const { data, onClose } = props;
// router
const router = useAppRouter();
// store hooks
const { captureWorkspaceEvent } = useEventTracker();
const { deleteWorkspace } = useWorkspace();
const { t } = useTranslation();
// form info
const {
control,
formState: { errors, isSubmitting },
handleSubmit,
reset,
watch,
} = useForm({ defaultValues });
const canDelete = watch("workspaceName") === data?.name && watch("confirmDelete") === "delete my workspace";
const handleClose = () => {
onClose();
reset(defaultValues);
};
const onSubmit = async () => {
if (!data || !canDelete) return;
await deleteWorkspace(data.slug)
.then(() => {
handleClose();
router.push("/profile");
captureWorkspaceEvent({
eventName: WORKSPACE_DELETED,
payload: {
...data,
state: "SUCCESS",
element: "Workspace general settings page",
},
});
setToast({
type: TOAST_TYPE.SUCCESS,
title: t("workspace_settings.settings.general.delete_modal.success_title"),
message: t("workspace_settings.settings.general.delete_modal.success_message"),
});
})
.catch(() => {
setToast({
type: TOAST_TYPE.ERROR,
title: t("workspace_settings.settings.general.delete_modal.error_title"),
message: t("workspace_settings.settings.general.delete_modal.error_message"),
});
captureWorkspaceEvent({
eventName: WORKSPACE_DELETED,
payload: {
...data,
state: "FAILED",
element: "Workspace general settings page",
},
});
});
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-6 p-6">
<div className="flex flex-col sm:flex-row items-center sm:items-start gap-4">
<span
className={cn(
"flex-shrink-0 grid place-items-center rounded-full size-12 sm:size-10 bg-red-500/20 text-red-100"
)}
>
<AlertTriangle className="size-5 text-red-600" aria-hidden="true" />
</span>
<div>
<div className="text-center sm:text-left">
<h3 className="text-lg font-medium">{t("workspace_settings.settings.general.delete_modal.title")}</h3>
<p className="mt-1 text-sm text-custom-text-200">
You are about to delete the workspace <span className="break-words font-semibold">{data?.name}</span>. If
you confirm, you will lose access to all your work data in this workspace without any way to restore it.
Tread very carefully.
</p>
</div>
<div className="text-custom-text-200 mt-4">
<p className="break-words text-sm ">Type in this workspace&apos;s name to continue.</p>
<Controller
control={control}
name="workspaceName"
render={({ field: { value, onChange, ref } }) => (
<Input
id="workspaceName"
name="workspaceName"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.workspaceName)}
placeholder={data?.name}
className="mt-2 w-full"
autoComplete="off"
/>
)}
/>
</div>
<div className="text-custom-text-200 mt-4">
<p className="text-sm">
For final confirmation, type{" "}
<span className="font-medium text-custom-text-100">delete my workspace </span>
below.
</p>
<Controller
control={control}
name="confirmDelete"
render={({ field: { value, onChange, ref } }) => (
<Input
id="confirmDelete"
name="confirmDelete"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.confirmDelete)}
placeholder=""
className="mt-2 w-full"
autoComplete="off"
/>
)}
/>
</div>
</div>
</div>
<div className="flex justify-end gap-2">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
{t("cancel")}
</Button>
<Button variant="danger" size="sm" type="submit" disabled={!canDelete} loading={isSubmitting}>
{isSubmitting ? t("deleting") : t("confirm")}
</Button>
</div>
</form>
);
});

View file

@ -1,205 +0,0 @@
"use client";
import React from "react";
import { observer } from "mobx-react";
import { Controller, useForm } from "react-hook-form";
import { AlertTriangle } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
// types
import { WORKSPACE_DELETED } from "@plane/constants";
import type { IWorkspace } from "@plane/types";
// ui
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// constants
// hooks
import { useEventTracker, useWorkspace } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
type Props = {
isOpen: boolean;
data: IWorkspace | null;
onClose: () => void;
};
const defaultValues = {
workspaceName: "",
confirmDelete: "",
};
export const DeleteWorkspaceModal: React.FC<Props> = observer((props) => {
const { isOpen, data, onClose } = props;
// router
const router = useAppRouter();
// store hooks
const { captureWorkspaceEvent } = useEventTracker();
const { deleteWorkspace } = useWorkspace();
// form info
const {
control,
formState: { errors, isSubmitting },
handleSubmit,
reset,
watch,
} = useForm({ defaultValues });
const canDelete = watch("workspaceName") === data?.name && watch("confirmDelete") === "delete my workspace";
const handleClose = () => {
const timer = setTimeout(() => {
reset(defaultValues);
clearTimeout(timer);
}, 350);
onClose();
};
const onSubmit = async () => {
if (!data || !canDelete) return;
await deleteWorkspace(data.slug)
.then(() => {
handleClose();
router.push("/");
captureWorkspaceEvent({
eventName: WORKSPACE_DELETED,
payload: {
...data,
state: "SUCCESS",
element: "Workspace general settings page",
},
});
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Workspace deleted successfully.",
});
})
.catch(() => {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Something went wrong. Please try again later.",
});
captureWorkspaceEvent({
eventName: WORKSPACE_DELETED,
payload: {
...data,
state: "FAILED",
element: "Workspace general settings page",
},
});
});
};
return (
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
<AlertTriangle className="h-6 w-6 text-red-600" aria-hidden="true" />
</span>
<span className="flex items-center justify-start">
<h3 className="text-xl font-medium 2xl:text-2xl">Delete workspace</h3>
</span>
</div>
<span>
<p className="text-sm leading-7 text-custom-text-200">
Are you sure you want to delete workspace{" "}
<span className="break-words font-semibold">{data?.name}</span>? All of the data related to the
workspace will be permanently removed. This action cannot be undone.
</p>
</span>
<div className="text-custom-text-200">
<p className="break-words text-sm ">
Enter the workspace name <span className="font-medium text-custom-text-100">{data?.name}</span> to
continue:
</p>
<Controller
control={control}
name="workspaceName"
render={({ field: { value, onChange, ref } }) => (
<Input
id="workspaceName"
name="workspaceName"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.workspaceName)}
placeholder="Workspace name"
className="mt-2 w-full"
autoComplete="off"
/>
)}
/>
</div>
<div className="text-custom-text-200">
<p className="text-sm">
To confirm, type <span className="font-medium text-custom-text-100">delete my workspace</span>{" "}
below:
</p>
<Controller
control={control}
name="confirmDelete"
render={({ field: { value, onChange, ref } }) => (
<Input
id="confirmDelete"
name="confirmDelete"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.confirmDelete)}
placeholder="Enter 'delete my workspace'"
className="mt-2 w-full"
autoComplete="off"
/>
)}
/>
</div>
<div className="flex justify-end gap-2">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="danger" size="sm" type="submit" disabled={!canDelete} loading={isSubmitting}>
{isSubmitting ? "Deleting" : "Delete workspace"}
</Button>
</div>
</form>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
});

View file

@ -3,6 +3,5 @@ export * from "./sidebar";
export * from "./views"; export * from "./views";
export * from "./confirm-workspace-member-remove"; export * from "./confirm-workspace-member-remove";
export * from "./create-workspace-form"; export * from "./create-workspace-form";
export * from "./delete-workspace-modal";
export * from "./logo"; export * from "./logo";
export * from "./invite-modal"; export * from "./invite-modal";

View file

@ -0,0 +1 @@
export * from "ce/components/workspace/delete-workspace-modal";