🚸 (dashboard) Add unpublish menu item in dashboard
Also remove the useless publishedTypebotId field in Typebot Closes #232
This commit is contained in:
@@ -12,7 +12,6 @@ export type NewTypebotProps = Omit<
|
||||
| 'createdAt'
|
||||
| 'updatedAt'
|
||||
| 'id'
|
||||
| 'publishedTypebotId'
|
||||
| 'publicId'
|
||||
| 'customDomain'
|
||||
| 'icon'
|
||||
|
||||
@@ -56,7 +56,6 @@ const duplicateTypebot = (
|
||||
...typebot,
|
||||
id,
|
||||
name: `${typebot.name} copy`,
|
||||
publishedTypebotId: null,
|
||||
publicId: null,
|
||||
customDomain: null,
|
||||
groups: typebot.groups.map((b) => ({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Typebot } from 'models'
|
||||
|
||||
export type TypebotInDashboard = Pick<
|
||||
Typebot,
|
||||
'id' | 'name' | 'publishedTypebotId' | 'icon'
|
||||
>
|
||||
export type TypebotInDashboard = Pick<Typebot, 'id' | 'name' | 'icon'> & {
|
||||
publishedTypebotId?: string
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { RightPanel, useEditor } from '../../providers/EditorProvider'
|
||||
import { useTypebot } from '../../providers/TypebotProvider'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useState } from 'react'
|
||||
import { isNotDefined } from 'utils'
|
||||
import { isDefined, isNotDefined } from 'utils'
|
||||
import { EditableTypebotName } from './EditableTypebotName'
|
||||
import { getBubbleActions } from 'typebot-js'
|
||||
import Link from 'next/link'
|
||||
@@ -34,6 +34,7 @@ export const TypebotHeader = () => {
|
||||
const router = useRouter()
|
||||
const {
|
||||
typebot,
|
||||
publishedTypebot,
|
||||
updateTypebot,
|
||||
save,
|
||||
undo,
|
||||
@@ -126,7 +127,7 @@ export const TypebotHeader = () => {
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
{typebot?.publishedTypebotId && (
|
||||
{isDefined(publishedTypebot) && (
|
||||
<Button
|
||||
as={Link}
|
||||
href={`/typebots/${typebot?.id}/results`}
|
||||
|
||||
@@ -16,14 +16,13 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { isDefined, isNotDefined, omit } from 'utils'
|
||||
import { isDefined, omit } from 'utils'
|
||||
import { edgesAction, EdgesActions } from './actions/edges'
|
||||
import { itemsAction, ItemsActions } from './actions/items'
|
||||
import { GroupsActions, groupsActions } from './actions/groups'
|
||||
import { blocksAction, BlocksActions } from './actions/blocks'
|
||||
import { variablesAction, VariablesActions } from './actions/variables'
|
||||
import { dequal } from 'dequal'
|
||||
import cuid from 'cuid'
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { useTypebotQuery } from '@/hooks/useTypebotQuery'
|
||||
import useUndo from '../../hooks/useUndo'
|
||||
@@ -52,7 +51,6 @@ type UpdateTypebotPayload = Partial<{
|
||||
settings: Settings
|
||||
publicId: string
|
||||
name: string
|
||||
publishedTypebotId: string
|
||||
icon: string
|
||||
customDomain: string
|
||||
resultsTablePreferences: ResultsTablePreferences
|
||||
@@ -254,17 +252,12 @@ export const TypebotProvider = ({
|
||||
|
||||
const publishTypebot = async () => {
|
||||
if (!localTypebot) return
|
||||
const publishedTypebotId = cuid()
|
||||
const newLocalTypebot = { ...localTypebot }
|
||||
if (publishedTypebot && isNotDefined(localTypebot.publishedTypebotId)) {
|
||||
updateLocalTypebot({ publishedTypebotId: publishedTypebot.id })
|
||||
await saveTypebot()
|
||||
}
|
||||
if (!publishedTypebot) {
|
||||
const newPublicId =
|
||||
localTypebot.publicId ??
|
||||
parseDefaultPublicId(localTypebot.name, localTypebot.id)
|
||||
updateLocalTypebot({ publicId: newPublicId, publishedTypebotId })
|
||||
updateLocalTypebot({ publicId: newPublicId })
|
||||
newLocalTypebot.publicId = newPublicId
|
||||
await saveTypebot()
|
||||
}
|
||||
@@ -277,8 +270,7 @@ export const TypebotProvider = ({
|
||||
setIsPublishing(true)
|
||||
const { data, error } = await createPublishedTypebotQuery(
|
||||
{
|
||||
...parseTypebotToPublicTypebot(newLocalTypebot),
|
||||
id: publishedTypebotId,
|
||||
...omit(parseTypebotToPublicTypebot(newLocalTypebot), 'id'),
|
||||
},
|
||||
localTypebot.workspaceId
|
||||
)
|
||||
|
||||
@@ -104,7 +104,7 @@ export const FolderContent = ({ folder }: Props) => {
|
||||
if (newFolder) mutateFolders({ folders: [...folders, newFolder] })
|
||||
}
|
||||
|
||||
const handleTypebotDeleted = () => {
|
||||
const handleTypebotUpdated = () => {
|
||||
if (!typebots) return
|
||||
refetchTypebots()
|
||||
}
|
||||
@@ -208,7 +208,7 @@ export const FolderContent = ({ folder }: Props) => {
|
||||
<TypebotButton
|
||||
key={typebot.id.toString()}
|
||||
typebot={typebot}
|
||||
onTypebotDeleted={handleTypebotDeleted}
|
||||
onTypebotUpdated={handleTypebotUpdated}
|
||||
onMouseDown={handleMouseDown(typebot)}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
import { useRouter } from 'next/router'
|
||||
import { ConfirmModal } from '@/components/ConfirmModal'
|
||||
import { GripIcon } from '@/components/icons'
|
||||
import { Typebot } from 'models'
|
||||
import { useTypebotDnd } from '../TypebotDndProvider'
|
||||
import { useDebounce } from 'use-debounce'
|
||||
import { Plan } from 'db'
|
||||
@@ -24,23 +23,25 @@ import {
|
||||
getTypebotQuery,
|
||||
deleteTypebotQuery,
|
||||
importTypebotQuery,
|
||||
TypebotInDashboard,
|
||||
} from '@/features/dashboard'
|
||||
import { MoreButton } from './MoreButton'
|
||||
import { EmojiOrImageIcon } from '@/components/EmojiOrImageIcon'
|
||||
import { deletePublishedTypebotQuery } from '@/features/publish/queries/deletePublishedTypebotQuery'
|
||||
|
||||
type ChatbotCardProps = {
|
||||
typebot: Pick<Typebot, 'id' | 'publishedTypebotId' | 'name' | 'icon'>
|
||||
type Props = {
|
||||
typebot: TypebotInDashboard
|
||||
isReadOnly?: boolean
|
||||
onTypebotDeleted?: () => void
|
||||
onTypebotUpdated: () => void
|
||||
onMouseDown?: (e: React.MouseEvent<HTMLButtonElement>) => void
|
||||
}
|
||||
|
||||
export const TypebotButton = ({
|
||||
typebot,
|
||||
onTypebotDeleted,
|
||||
isReadOnly = false,
|
||||
onTypebotUpdated,
|
||||
onMouseDown,
|
||||
}: ChatbotCardProps) => {
|
||||
}: Props) => {
|
||||
const router = useRouter()
|
||||
const { workspace } = useWorkspace()
|
||||
const { draggedTypebot } = useTypebotDnd()
|
||||
@@ -70,7 +71,7 @@ export const TypebotButton = ({
|
||||
title: "Couldn't delete typebot",
|
||||
description: error.message,
|
||||
})
|
||||
if (onTypebotDeleted) onTypebotDeleted()
|
||||
onTypebotUpdated()
|
||||
}
|
||||
|
||||
const handleDuplicateClick = async (e: React.MouseEvent) => {
|
||||
@@ -95,6 +96,17 @@ export const TypebotButton = ({
|
||||
onDeleteOpen()
|
||||
}
|
||||
|
||||
const handleUnpublishClick = async (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
if (!typebot.publishedTypebotId) return
|
||||
const { error } = await deletePublishedTypebotQuery({
|
||||
publishedTypebotId: typebot.publishedTypebotId,
|
||||
typebotId: typebot.id,
|
||||
})
|
||||
if (error) showToast({ description: error.message })
|
||||
else onTypebotUpdated()
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
as={WrapItem}
|
||||
@@ -143,6 +155,9 @@ export const TypebotButton = ({
|
||||
right="20px"
|
||||
aria-label={`Show ${typebot.name} menu`}
|
||||
>
|
||||
{typebot.publishedTypebotId && (
|
||||
<MenuItem onClick={handleUnpublishClick}>Unpublish</MenuItem>
|
||||
)}
|
||||
<MenuItem onClick={handleDuplicateClick}>Duplicate</MenuItem>
|
||||
<MenuItem color="red" onClick={handleDeleteClick}>
|
||||
Delete
|
||||
|
||||
@@ -29,7 +29,7 @@ import { integrationsList } from './embeds/EmbedButton'
|
||||
|
||||
export const SharePage = () => {
|
||||
const { workspace } = useWorkspace()
|
||||
const { typebot, updateTypebot } = useTypebot()
|
||||
const { typebot, updateTypebot, publishedTypebot } = useTypebot()
|
||||
const { showToast } = useToast()
|
||||
|
||||
const handlePublicIdChange = async (publicId: string) => {
|
||||
@@ -39,7 +39,7 @@ export const SharePage = () => {
|
||||
const publicId = typebot
|
||||
? typebot?.publicId ?? parseDefaultPublicId(typebot.name, typebot.id)
|
||||
: ''
|
||||
const isPublished = isDefined(typebot?.publishedTypebotId)
|
||||
const isPublished = isDefined(publishedTypebot)
|
||||
|
||||
const handlePathnameChange = (pathname: string) => {
|
||||
if (!typebot?.customDomain) return
|
||||
|
||||
@@ -2,7 +2,7 @@ import { PublicTypebot } from 'models'
|
||||
import { sendRequest } from 'utils'
|
||||
|
||||
export const createPublishedTypebotQuery = async (
|
||||
typebot: PublicTypebot,
|
||||
typebot: Omit<PublicTypebot, 'id'>,
|
||||
workspaceId: string
|
||||
) =>
|
||||
sendRequest<PublicTypebot>({
|
||||
|
||||
@@ -20,7 +20,6 @@ export const parsePublicTypebotToTypebot = (
|
||||
customDomain: existingTypebot.customDomain,
|
||||
createdAt: existingTypebot.createdAt,
|
||||
updatedAt: existingTypebot.updatedAt,
|
||||
publishedTypebotId: typebot.id,
|
||||
folderId: existingTypebot.folderId,
|
||||
icon: existingTypebot.icon,
|
||||
workspaceId: existingTypebot.workspaceId,
|
||||
|
||||
@@ -2,7 +2,8 @@ import prisma from '@/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/utils/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { WorkspaceRole } from 'db'
|
||||
import { Typebot, typebotSchema } from 'models'
|
||||
import { PublicTypebot, Typebot, typebotSchema } from 'models'
|
||||
import { omit } from 'utils'
|
||||
import { z } from 'zod'
|
||||
|
||||
export const listTypebotsProcedure = authenticatedProcedure
|
||||
@@ -19,12 +20,13 @@ export const listTypebotsProcedure = authenticatedProcedure
|
||||
.output(
|
||||
z.object({
|
||||
typebots: z.array(
|
||||
typebotSchema.pick({
|
||||
name: true,
|
||||
icon: true,
|
||||
id: true,
|
||||
publishedTypebotId: true,
|
||||
})
|
||||
typebotSchema
|
||||
.pick({
|
||||
name: true,
|
||||
icon: true,
|
||||
id: true,
|
||||
})
|
||||
.and(z.object({ publishedTypebotId: z.string().optional() }))
|
||||
),
|
||||
})
|
||||
)
|
||||
@@ -58,11 +60,23 @@ export const listTypebotsProcedure = authenticatedProcedure
|
||||
],
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
select: { name: true, publishedTypebotId: true, id: true, icon: true },
|
||||
})) as Pick<Typebot, 'name' | 'id' | 'icon' | 'publishedTypebotId'>[]
|
||||
select: {
|
||||
name: true,
|
||||
publishedTypebot: { select: { id: true } },
|
||||
id: true,
|
||||
icon: true,
|
||||
},
|
||||
})) as (Pick<Typebot, 'name' | 'id' | 'icon'> & {
|
||||
publishedTypebot: Pick<PublicTypebot, 'id'>
|
||||
})[]
|
||||
|
||||
if (!typebots)
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'No typebots found' })
|
||||
|
||||
return { typebots }
|
||||
return {
|
||||
typebots: typebots.map((typebot) => ({
|
||||
publishedTypebotId: typebot.publishedTypebot?.id,
|
||||
...omit(typebot, 'publishedTypebot'),
|
||||
})),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -80,7 +80,7 @@ test('can update workspace info', async ({ page }) => {
|
||||
await page.fill('input[value="Pro workspace"]', 'My awesome workspace')
|
||||
await page.getByTestId('typebot-logo').click({ force: true })
|
||||
await expect(
|
||||
page.getByRole('button', { name: '🏦 My awesome workspace Pro' })
|
||||
page.getByRole('button', { name: 'My awesome workspace Pro' })
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user