mirror of
https://github.com/formbricks/formbricks.git
synced 2026-03-13 11:09:29 -05:00
feat: added delete team functionality
This commit is contained in:
@@ -1,61 +1,56 @@
|
||||
"use client";
|
||||
|
||||
import toast from "react-hot-toast";
|
||||
import DeleteDialog from "@/components/shared/DeleteDialog";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import { useEnvironment } from "@/lib/environments/environments";
|
||||
import { useState, Dispatch, SetStateAction } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMembers } from "@/lib/members";
|
||||
import { useProduct } from "@/lib/products/products";
|
||||
import { useProfile } from "@/lib/profile";
|
||||
import { truncate } from "@/lib/utils";
|
||||
import { Button, ErrorComponent, Input } from "@formbricks/ui";
|
||||
import { useTeamMutation } from "@/lib/teams/mutateTeams";
|
||||
import { useTeam } from "@/lib/teams/teams";
|
||||
import { useState, Dispatch, SetStateAction } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTeam, deleteTeam } from "@/lib/teams/teams";
|
||||
import { useMemberships } from "@/lib/memberships";
|
||||
|
||||
export default function DeleteTeam({ environmentId }) {
|
||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const { profile } = useProfile();
|
||||
const { memberships } = useMemberships();
|
||||
const { team } = useMembers(environmentId);
|
||||
const { product, isLoadingProduct, isErrorProduct } = useProduct(environmentId);
|
||||
const { environment } = useEnvironment(environmentId);
|
||||
const { team: teamData } = useTeam(environmentId);
|
||||
|
||||
console.log({ profile, team, product, environment, teamData, memberships });
|
||||
const { team, isErrorTeam: isErrorTeamMembers } = useMembers(environmentId);
|
||||
const { team: teamData, isLoadingTeam, isErrorTeam } = useTeam(environmentId);
|
||||
|
||||
const availableTeams = memberships?.length;
|
||||
const role = team?.members?.filter((member) => member?.userId === profile?.id)[0]?.role;
|
||||
const isUserAdminOrOwner = role === "admin" || role === "owner";
|
||||
const isDeleteDisabled = availableTeams <= 1 || !isUserAdminOrOwner;
|
||||
const isUserOwner = role === "owner";
|
||||
const isDeleteDisabled = availableTeams <= 1 || !isUserOwner;
|
||||
|
||||
if (isLoadingProduct) {
|
||||
if (isLoadingTeam) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
if (isErrorProduct) {
|
||||
if (isErrorTeam) {
|
||||
return <ErrorComponent />;
|
||||
}
|
||||
|
||||
// const handleDeleteProduct = async () => {
|
||||
// if (environment?.availableProducts?.length <= 1) {
|
||||
// toast.error("Cannot delete product. Your team needs at least 1.");
|
||||
// setIsDeleteDialogOpen(false);
|
||||
// return;
|
||||
// }
|
||||
// const deleteProductRes = await deleteProduct(environmentId);
|
||||
const handleDeleteTeam = async () => {
|
||||
if (memberships?.length <= 1) {
|
||||
toast.error("Cannot delete team. You need at least 1.");
|
||||
setIsDeleteDialogOpen(false);
|
||||
return;
|
||||
}
|
||||
const deleteTeamRes = await deleteTeam(environmentId);
|
||||
|
||||
// if (deleteProductRes?.id?.length > 0) {
|
||||
// toast.success("Product deleted successfully.");
|
||||
// // router.push("/");
|
||||
// } else if (deleteProductRes?.message?.length > 0) {
|
||||
// toast.error(deleteProductRes.message);
|
||||
// setIsDeleteDialogOpen(false);
|
||||
// } else {
|
||||
// toast.error("Error deleting product. Please try again.");
|
||||
// }
|
||||
// };
|
||||
if (deleteTeamRes?.deletedTeam?.id?.length > 0) {
|
||||
toast.success("Team deleted successfully.");
|
||||
router.push("/");
|
||||
} else if (deleteTeamRes?.message?.length > 0) {
|
||||
toast.error(deleteTeamRes.message);
|
||||
setIsDeleteDialogOpen(false);
|
||||
} else {
|
||||
toast.error("Error deleting team. Please try again.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -77,12 +72,17 @@ export default function DeleteTeam({ environmentId }) {
|
||||
)}
|
||||
{isDeleteDisabled && (
|
||||
<p className="text-sm text-red-700">
|
||||
{!isUserAdminOrOwner
|
||||
? "Only Admin or Owners can delete teams."
|
||||
{!isUserOwner
|
||||
? "Only Owner can delete the team."
|
||||
: "This is your only team, it cannot be deleted. Create a new team first."}
|
||||
</p>
|
||||
)}
|
||||
<DeleteTeamModal open={isDeleteDialogOpen} setOpen={setIsDeleteDialogOpen} teamData={teamData} />
|
||||
<DeleteTeamModal
|
||||
open={isDeleteDialogOpen}
|
||||
setOpen={setIsDeleteDialogOpen}
|
||||
teamData={teamData}
|
||||
deleteTeam={handleDeleteTeam}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -91,64 +91,41 @@ interface DeleteTeamModalProps {
|
||||
open: boolean;
|
||||
setOpen: Dispatch<SetStateAction<boolean>>;
|
||||
teamData: { name: string; id: string; plan: string };
|
||||
deleteTeam: () => void;
|
||||
}
|
||||
|
||||
function DeleteTeamModal({ setOpen, open, teamData }: DeleteTeamModalProps) {
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
function DeleteTeamModal({ setOpen, open, teamData, deleteTeam }: DeleteTeamModalProps) {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
setInputValue(e.target.value);
|
||||
};
|
||||
|
||||
const deleteTeam = async () => {
|
||||
try {
|
||||
setDeleting(true);
|
||||
// await deleteProfile();
|
||||
// await signOut();
|
||||
// await formbricksLogout();
|
||||
} catch (error) {
|
||||
toast.error("Something went wrong");
|
||||
} finally {
|
||||
setDeleting(false);
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DeleteDialog
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
deleteWhat="team"
|
||||
onDelete={() => deleteTeam()}
|
||||
onDelete={deleteTeam}
|
||||
text="Before you proceed with deleting this team, please be aware of the following consequences:"
|
||||
isDeleting={deleting}
|
||||
disabled={inputValue !== teamData.name}>
|
||||
disabled={inputValue !== teamData?.name}>
|
||||
<div className="py-5">
|
||||
<ul className="list-disc pb-6 pl-6">
|
||||
<li>
|
||||
Permanent removal of all <b>products linked to this team</b>. This includes all surveys,
|
||||
responses, user actions and attributes associated with these products.
|
||||
</li>
|
||||
<li>
|
||||
If you are the owner of a team with other admins, the ownership of that team will be transferred
|
||||
to another admin.
|
||||
</li>
|
||||
<li>
|
||||
If you are the only member of a team or there is no other admin present, the team will be
|
||||
irreversibly deleted along with all associated data.
|
||||
</li>
|
||||
<li>This action cannot be undone. If it's gone, it's gone.</li>
|
||||
</ul>
|
||||
<form>
|
||||
<label htmlFor="deleteTeamConfirmation">
|
||||
Please enter <b>{teamData.name}</b> in the following field to confirm the definitive deletion of
|
||||
Please enter <b>{teamData?.name}</b> in the following field to confirm the definitive deletion of
|
||||
this team:
|
||||
</label>
|
||||
<Input
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
placeholder={teamData.name}
|
||||
placeholder={teamData?.name}
|
||||
className="mt-5"
|
||||
type="text"
|
||||
id="deleteTeamConfirmation"
|
||||
|
||||
@@ -15,3 +15,10 @@ export const useTeam = (environmentId: string) => {
|
||||
mutateTeam: mutate,
|
||||
};
|
||||
};
|
||||
|
||||
export const deleteTeam = async (environmentId: string) => {
|
||||
const response = await fetch(`/api/v1/environments/${environmentId}/team/`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
return response.json();
|
||||
};
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { getSessionUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const currentUser: any = await getSessionUser(req, res);
|
||||
if (!currentUser) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
const environmentId = req.query?.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
@@ -47,6 +51,65 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
return res.json(team);
|
||||
}
|
||||
|
||||
// DELETE
|
||||
else if (req.method === "DELETE") {
|
||||
try {
|
||||
const environment = await prisma.environment.findUnique({
|
||||
where: {
|
||||
id: environmentId,
|
||||
},
|
||||
select: {
|
||||
product: {
|
||||
select: {
|
||||
teamId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (environment === null) {
|
||||
return res.status(404).json({ message: "This environment doesn't exist" });
|
||||
}
|
||||
const team = await prisma.team.findUnique({
|
||||
where: {
|
||||
id: environment.product.teamId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
stripeCustomerId: true,
|
||||
plan: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (team === null) {
|
||||
return res.status(404).json({ message: "This team doesn't exist" });
|
||||
}
|
||||
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_teamId: {
|
||||
userId: currentUser.id,
|
||||
teamId: team.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (membership?.role !== "owner") {
|
||||
return res.status(403).json({ message: "You are not allowed to delete this team" });
|
||||
}
|
||||
|
||||
const prismaRes = await prisma.team.delete({
|
||||
where: {
|
||||
id: team.id,
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).json({ deletedTeam: prismaRes });
|
||||
} catch (error) {
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
}
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
|
||||
56
pnpm-lock.yaml
generated
56
pnpm-lock.yaml
generated
@@ -1,4 +1,4 @@
|
||||
lockfileVersion: '6.0'
|
||||
lockfileVersion: '6.1'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
@@ -19,7 +19,7 @@ importers:
|
||||
version: 3.12.7
|
||||
turbo:
|
||||
specifier: latest
|
||||
version: 1.10.7
|
||||
version: 1.10.3
|
||||
|
||||
apps/demo:
|
||||
dependencies:
|
||||
@@ -352,7 +352,7 @@ importers:
|
||||
version: 8.9.0(eslint@8.46.0)
|
||||
eslint-config-turbo:
|
||||
specifier: latest
|
||||
version: 1.8.8(eslint@8.46.0)
|
||||
version: 1.10.3(eslint@8.46.0)
|
||||
eslint-plugin-react:
|
||||
specifier: 7.33.1
|
||||
version: 7.33.1(eslint@8.46.0)
|
||||
@@ -9808,13 +9808,13 @@ packages:
|
||||
eslint: 8.46.0
|
||||
dev: true
|
||||
|
||||
/eslint-config-turbo@1.8.8(eslint@8.46.0):
|
||||
resolution: {integrity: sha512-+yT22sHOT5iC1sbBXfLIdXfbZuiv9bAyOXsxTxFCWelTeFFnANqmuKB3x274CFvf7WRuZ/vYP/VMjzU9xnFnxA==}
|
||||
/eslint-config-turbo@1.10.3(eslint@8.46.0):
|
||||
resolution: {integrity: sha512-ggzPfTJfMsMS383oZ4zfTP1zQvyMyiigOQJRUnLt1nqII6SKkTzdKZdwmXRDHU24KFwUfEFtT6c8vnm2VhL0uQ==}
|
||||
peerDependencies:
|
||||
eslint: '>6.6.0'
|
||||
dependencies:
|
||||
eslint: 8.46.0
|
||||
eslint-plugin-turbo: 1.8.8(eslint@8.46.0)
|
||||
eslint-plugin-turbo: 1.10.3(eslint@8.46.0)
|
||||
dev: true
|
||||
|
||||
/eslint-import-resolver-node@0.3.6:
|
||||
@@ -10002,8 +10002,8 @@ packages:
|
||||
semver: 6.3.1
|
||||
string.prototype.matchall: 4.0.8
|
||||
|
||||
/eslint-plugin-turbo@1.8.8(eslint@8.46.0):
|
||||
resolution: {integrity: sha512-zqyTIvveOY4YU5jviDWw9GXHd4RiKmfEgwsjBrV/a965w0PpDwJgEUoSMB/C/dU310Sv9mF3DSdEjxjJLaw6rA==}
|
||||
/eslint-plugin-turbo@1.10.3(eslint@8.46.0):
|
||||
resolution: {integrity: sha512-g3Mnnk7el1FqxHfqbE/MayLvCsYjA/vKmAnUj66kV4AlM7p/EZqdt42NMcMSKtDVEm0w+utQkkzWG2Xsa0Pd/g==}
|
||||
peerDependencies:
|
||||
eslint: '>6.6.0'
|
||||
dependencies:
|
||||
@@ -19763,65 +19763,65 @@ packages:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
/turbo-darwin-64@1.10.7:
|
||||
resolution: {integrity: sha512-N2MNuhwrl6g7vGuz4y3fFG2aR1oCs0UZ5HKl8KSTn/VC2y2YIuLGedQ3OVbo0TfEvygAlF3QGAAKKtOCmGPNKA==}
|
||||
/turbo-darwin-64@1.10.3:
|
||||
resolution: {integrity: sha512-IIB9IomJGyD3EdpSscm7Ip1xVWtYb7D0x7oH3vad3gjFcjHJzDz9xZ/iw/qItFEW+wGFcLSRPd+1BNnuLM8AsA==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-darwin-arm64@1.10.7:
|
||||
resolution: {integrity: sha512-WbJkvjU+6qkngp7K4EsswOriO3xrNQag7YEGRtfLoDdMTk4O4QTeU6sfg2dKfDsBpTidTvEDwgIYJhYVGzrz9Q==}
|
||||
/turbo-darwin-arm64@1.10.3:
|
||||
resolution: {integrity: sha512-SBNmOZU9YEB0eyNIxeeQ+Wi0Ufd+nprEVp41rgUSRXEIpXjsDjyBnKnF+sQQj3+FLb4yyi/yZQckB+55qXWEsw==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-linux-64@1.10.7:
|
||||
resolution: {integrity: sha512-x1CF2CDP1pDz/J8/B2T0hnmmOQI2+y11JGIzNP0KtwxDM7rmeg3DDTtDM/9PwGqfPotN9iVGgMiMvBuMFbsLhg==}
|
||||
/turbo-linux-64@1.10.3:
|
||||
resolution: {integrity: sha512-kvAisGKE7xHJdyMxZLvg53zvHxjqPK1UVj4757PQqtx9dnjYHSc8epmivE6niPgDHon5YqImzArCjVZJYpIGHQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-linux-arm64@1.10.7:
|
||||
resolution: {integrity: sha512-JtnBmaBSYbs7peJPkXzXxsRGSGBmBEIb6/kC8RRmyvPAMyqF8wIex0pttsI+9plghREiGPtRWv/lfQEPRlXnNQ==}
|
||||
/turbo-linux-arm64@1.10.3:
|
||||
resolution: {integrity: sha512-Qgaqln0IYRgyL0SowJOi+PNxejv1I2xhzXOI+D+z4YHbgSx87ox1IsALYBlK8VRVYY8VCXl+PN12r1ioV09j7A==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-windows-64@1.10.7:
|
||||
resolution: {integrity: sha512-7A/4CByoHdolWS8dg3DPm99owfu1aY/W0V0+KxFd0o2JQMTQtoBgIMSvZesXaWM57z3OLsietFivDLQPuzE75w==}
|
||||
/turbo-windows-64@1.10.3:
|
||||
resolution: {integrity: sha512-rbH9wManURNN8mBnN/ZdkpUuTvyVVEMiUwFUX4GVE5qmV15iHtZfDLUSGGCP2UFBazHcpNHG1OJzgc55GFFrUw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-windows-arm64@1.10.7:
|
||||
resolution: {integrity: sha512-D36K/3b6+hqm9IBAymnuVgyePktwQ+F0lSXr2B9JfAdFPBktSqGmp50JNC7pahxhnuCLj0Vdpe9RqfnJw5zATA==}
|
||||
/turbo-windows-arm64@1.10.3:
|
||||
resolution: {integrity: sha512-ThlkqxhcGZX39CaTjsHqJnqVe+WImjX13pmjnpChz6q5HHbeRxaJSFzgrHIOt0sUUVx90W/WrNRyoIt/aafniw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo@1.10.7:
|
||||
resolution: {integrity: sha512-xm0MPM28TWx1e6TNC3wokfE5eaDqlfi0G24kmeHupDUZt5Wd0OzHFENEHMPqEaNKJ0I+AMObL6nbSZonZBV2HA==}
|
||||
/turbo@1.10.3:
|
||||
resolution: {integrity: sha512-U4gKCWcKgLcCjQd4Pl8KJdfEKumpyWbzRu75A6FCj6Ctea1PIm58W6Ltw1QXKqHrl2pF9e1raAskf/h6dlrPCA==}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
optionalDependencies:
|
||||
turbo-darwin-64: 1.10.7
|
||||
turbo-darwin-arm64: 1.10.7
|
||||
turbo-linux-64: 1.10.7
|
||||
turbo-linux-arm64: 1.10.7
|
||||
turbo-windows-64: 1.10.7
|
||||
turbo-windows-arm64: 1.10.7
|
||||
turbo-darwin-64: 1.10.3
|
||||
turbo-darwin-arm64: 1.10.3
|
||||
turbo-linux-64: 1.10.3
|
||||
turbo-linux-arm64: 1.10.3
|
||||
turbo-windows-64: 1.10.3
|
||||
turbo-windows-arm64: 1.10.3
|
||||
dev: true
|
||||
|
||||
/tween-functions@1.2.0:
|
||||
|
||||
Reference in New Issue
Block a user