diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/DeleteTeam.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/members/DeleteTeam.tsx index 34a51e4ac0..4637e4df85 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/members/DeleteTeam.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/members/DeleteTeam.tsx @@ -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 ; } - if (isErrorProduct) { + if (isErrorTeam) { return ; } - // 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 (
@@ -77,12 +72,17 @@ export default function DeleteTeam({ environmentId }) { )} {isDeleteDisabled && (

- {!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."}

)} - +
); } @@ -91,64 +91,41 @@ interface DeleteTeamModalProps { open: boolean; setOpen: Dispatch>; 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 ( 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}>
  • Permanent removal of all products linked to this team. This includes all surveys, responses, user actions and attributes associated with these products.
  • -
  • - If you are the owner of a team with other admins, the ownership of that team will be transferred - to another admin. -
  • -
  • - 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. -
  • This action cannot be undone. If it's gone, it's gone.
{ mutateTeam: mutate, }; }; + +export const deleteTeam = async (environmentId: string) => { + const response = await fetch(`/api/v1/environments/${environmentId}/team/`, { + method: "DELETE", + }); + return response.json(); +}; diff --git a/apps/web/pages/api/v1/environments/[environmentId]/team/index.ts b/apps/web/pages/api/v1/environments/[environmentId]/team/index.ts index 9a126c0fc1..27e63f69db 100644 --- a/apps/web/pages/api/v1/environments/[environmentId]/team/index.ts +++ b/apps/web/pages/api/v1/environments/[environmentId]/team/index.ts @@ -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.`); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index afe058769e..ec67ede3c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: