feat(dashboard): ⚡️ Delete workspace button
This commit is contained in:
@ -1,11 +1,21 @@
|
|||||||
import { Stack, FormControl, FormLabel, Flex } from '@chakra-ui/react'
|
import {
|
||||||
|
Stack,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
Flex,
|
||||||
|
Button,
|
||||||
|
useDisclosure,
|
||||||
|
Text,
|
||||||
|
} from '@chakra-ui/react'
|
||||||
|
import { ConfirmModal } from 'components/modals/ConfirmModal'
|
||||||
import { EditableEmojiOrImageIcon } from 'components/shared/EditableEmojiOrImageIcon'
|
import { EditableEmojiOrImageIcon } from 'components/shared/EditableEmojiOrImageIcon'
|
||||||
import { Input } from 'components/shared/Textbox'
|
import { Input } from 'components/shared/Textbox'
|
||||||
import { useWorkspace } from 'contexts/WorkspaceContext'
|
import { useWorkspace } from 'contexts/WorkspaceContext'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export const WorkspaceSettingsForm = () => {
|
export const WorkspaceSettingsForm = ({ onClose }: { onClose: () => void }) => {
|
||||||
const { workspace, updateWorkspace } = useWorkspace()
|
const { workspace, workspaces, updateWorkspace, deleteCurrentWorkspace } =
|
||||||
|
useWorkspace()
|
||||||
|
|
||||||
const handleNameChange = (name: string) => {
|
const handleNameChange = (name: string) => {
|
||||||
if (!workspace?.id) return
|
if (!workspace?.id) return
|
||||||
@ -17,6 +27,11 @@ export const WorkspaceSettingsForm = () => {
|
|||||||
updateWorkspace(workspace?.id, { icon })
|
updateWorkspace(workspace?.id, { icon })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDeleteClick = async () => {
|
||||||
|
await deleteCurrentWorkspace()
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing="6" w="full">
|
<Stack spacing="6" w="full">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@ -40,6 +55,41 @@ export const WorkspaceSettingsForm = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
{workspace && workspaces && workspaces.length > 1 && (
|
||||||
|
<DeleteWorkspaceButton
|
||||||
|
onConfirm={handleDeleteClick}
|
||||||
|
workspaceName={workspace?.name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DeleteWorkspaceButton = ({
|
||||||
|
workspaceName,
|
||||||
|
onConfirm,
|
||||||
|
}: {
|
||||||
|
workspaceName: string
|
||||||
|
onConfirm: () => Promise<void>
|
||||||
|
}) => {
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button colorScheme="red" variant="outline" onClick={onOpen}>
|
||||||
|
Delete workspace
|
||||||
|
</Button>
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onConfirm={onConfirm}
|
||||||
|
onClose={onClose}
|
||||||
|
message={
|
||||||
|
<Text>
|
||||||
|
Are you sure you want to delete {workspaceName} workspace? All its
|
||||||
|
folders, typebots and results will be deleted forever.'
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
confirmButtonLabel="Delete"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -132,21 +132,27 @@ export const WorkspaceSettingsModal = ({
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Flex flex="1" p="10">
|
<Flex flex="1" p="10">
|
||||||
<SettingsContent tab={selectedTab} />
|
<SettingsContent tab={selectedTab} onClose={onClose} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingsContent = ({ tab }: { tab: SettingsTab }) => {
|
const SettingsContent = ({
|
||||||
|
tab,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
tab: SettingsTab
|
||||||
|
onClose: () => void
|
||||||
|
}) => {
|
||||||
switch (tab) {
|
switch (tab) {
|
||||||
case 'my-account':
|
case 'my-account':
|
||||||
return <MyAccountForm />
|
return <MyAccountForm />
|
||||||
case 'user-settings':
|
case 'user-settings':
|
||||||
return <EditorSettings />
|
return <EditorSettings />
|
||||||
case 'workspace-settings':
|
case 'workspace-settings':
|
||||||
return <WorkspaceSettingsForm />
|
return <WorkspaceSettingsForm onClose={onClose} />
|
||||||
case 'members':
|
case 'members':
|
||||||
return <MembersList />
|
return <MembersList />
|
||||||
case 'billing':
|
case 'billing':
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
createNewWorkspace,
|
createNewWorkspace,
|
||||||
useWorkspaces,
|
useWorkspaces,
|
||||||
updateWorkspace as patchWorkspace,
|
updateWorkspace as patchWorkspace,
|
||||||
|
deleteWorkspace,
|
||||||
} from 'services/workspace/workspace'
|
} from 'services/workspace/workspace'
|
||||||
import { useUser } from './UserContext'
|
import { useUser } from './UserContext'
|
||||||
import { useTypebot } from './TypebotContext'
|
import { useTypebot } from './TypebotContext'
|
||||||
@ -29,6 +30,7 @@ const workspaceContext = createContext<{
|
|||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
updates: Partial<Workspace>
|
updates: Partial<Workspace>
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
|
deleteCurrentWorkspace: () => Promise<void>
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
}>({})
|
}>({})
|
||||||
@ -122,6 +124,20 @@ export const WorkspaceContext = ({ children }: { children: ReactNode }) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteCurrentWorkspace = async () => {
|
||||||
|
if (!currentWorkspace || !workspaces || workspaces.length < 2) return
|
||||||
|
const { data } = await deleteWorkspace(currentWorkspace.id)
|
||||||
|
if (!data || !currentWorkspace) return
|
||||||
|
setCurrentWorkspace(workspaces[0])
|
||||||
|
mutate({
|
||||||
|
workspaces: (workspaces ?? []).filter((w) =>
|
||||||
|
w.id === currentWorkspace.id
|
||||||
|
? { ...data.workspace, members: w.members }
|
||||||
|
: w
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<workspaceContext.Provider
|
<workspaceContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@ -133,6 +149,7 @@ export const WorkspaceContext = ({ children }: { children: ReactNode }) => {
|
|||||||
switchWorkspace,
|
switchWorkspace,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
updateWorkspace,
|
updateWorkspace,
|
||||||
|
deleteCurrentWorkspace,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -22,6 +22,18 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
workspace: updatedWorkspace,
|
workspace: updatedWorkspace,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if (req.method === 'DELETE') {
|
||||||
|
const id = req.query.workspaceId as string
|
||||||
|
await prisma.workspace.deleteMany({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
members: { some: { userId: user.id, role: WorkspaceRole.ADMIN } },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return res.status(200).json({
|
||||||
|
message: 'success',
|
||||||
|
})
|
||||||
|
}
|
||||||
methodNotAllowed(res)
|
methodNotAllowed(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ test('can switch between workspaces and access typebot', async ({ page }) => {
|
|||||||
await expect(page.locator('text="Hey there"')).toBeVisible()
|
await expect(page.locator('text="Hey there"')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('can create a new workspace', async ({ page }) => {
|
test('can create and delete a new workspace', async ({ page }) => {
|
||||||
await page.goto('/typebots')
|
await page.goto('/typebots')
|
||||||
await page.click("text=Pro user's workspace")
|
await page.click("text=Pro user's workspace")
|
||||||
await expect(
|
await expect(
|
||||||
@ -53,6 +53,20 @@ test('can create a new workspace', async ({ page }) => {
|
|||||||
await expect(
|
await expect(
|
||||||
page.locator('text="Pro user\'s workspace" >> nth=1')
|
page.locator('text="Pro user\'s workspace" >> nth=1')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
await page.click('text=Settings & Members')
|
||||||
|
await page.click('text="Settings"')
|
||||||
|
await page.click('text="Delete workspace"')
|
||||||
|
await expect(
|
||||||
|
page.locator(
|
||||||
|
"text=Are you sure you want to delete Pro user's workspace workspace?"
|
||||||
|
)
|
||||||
|
).toBeVisible()
|
||||||
|
await page.click('text="Delete"')
|
||||||
|
await expect(page.locator('text=Pro typebot')).toBeVisible()
|
||||||
|
await page.click("text=Pro user's workspace")
|
||||||
|
await expect(
|
||||||
|
page.locator('text="Pro user\'s workspace" >> nth=1')
|
||||||
|
).toBeHidden()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('can update workspace info', async ({ page }) => {
|
test('can update workspace info', async ({ page }) => {
|
||||||
|
@ -38,6 +38,14 @@ export const updateWorkspace = async (updates: Partial<Workspace>) =>
|
|||||||
body: updates,
|
body: updates,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const deleteWorkspace = (workspaceId: string) =>
|
||||||
|
sendRequest<{
|
||||||
|
workspace: Workspace
|
||||||
|
}>({
|
||||||
|
url: `/api/workspaces/${workspaceId}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
|
||||||
export const planToReadable = (plan?: Plan) => {
|
export const planToReadable = (plan?: Plan) => {
|
||||||
if (!plan) return
|
if (!plan) return
|
||||||
switch (plan) {
|
switch (plan) {
|
||||||
|
Reference in New Issue
Block a user