⚡ Auto continue bot on whatsApp if starting block is input (#849)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ### Summary by CodeRabbit **New Features:** - Added WhatsApp integration feature to the Pro plan. **Refactor:** - Introduced the ability to exclude specific plans from being displayed in the Change Plan Modal. - Renamed the function `isProPlan` to `hasProPerks`, enhancing code readability and maintainability. - Updated the `EmbedButton` component to handle a new `lockTagPlan` property and use the `modal` function instead of the `Modal` component. **Chore:** - Removed the `whatsAppPhoneNumberId` field from the `Typebot` model across various files, simplifying the data structure of the model. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@ -8,19 +8,23 @@ import {
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
import { ChangePlanModal } from '@/features/billing/components/ChangePlanModal'
|
||||
import {
|
||||
ChangePlanModal,
|
||||
ChangePlanModalProps,
|
||||
} from '@/features/billing/components/ChangePlanModal'
|
||||
import { useI18n } from '@/locales'
|
||||
|
||||
type Props = {
|
||||
contentLabel: React.ReactNode
|
||||
buttonLabel?: string
|
||||
type?: string
|
||||
} & AlertProps
|
||||
} & AlertProps &
|
||||
Pick<ChangePlanModalProps, 'type' | 'excludedPlans'>
|
||||
|
||||
export const UnlockPlanAlertInfo = ({
|
||||
contentLabel,
|
||||
buttonLabel,
|
||||
type,
|
||||
excludedPlans,
|
||||
...props
|
||||
}: Props) => {
|
||||
const t = useI18n()
|
||||
@ -45,7 +49,12 @@ export const UnlockPlanAlertInfo = ({
|
||||
>
|
||||
{buttonLabel ?? t('billing.upgradeAlert.buttonDefaultLabel')}
|
||||
</Button>
|
||||
<ChangePlanModal isOpen={isOpen} onClose={onClose} type={type} />
|
||||
<ChangePlanModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
type={type}
|
||||
excludedPlans={excludedPlans}
|
||||
/>
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
|
@ -82,6 +82,7 @@ export const AnalyticsGraphContainer = ({ stats }: { stats?: Stats }) => {
|
||||
onClose={onClose}
|
||||
isOpen={isOpen}
|
||||
type={t('billing.limitMessage.analytics')}
|
||||
excludedPlans={['STARTER']}
|
||||
/>
|
||||
<StatsCards stats={stats} pos="absolute" />
|
||||
</Flex>
|
||||
|
@ -16,9 +16,10 @@ import { StripeClimateLogo } from './StripeClimateLogo'
|
||||
|
||||
type Props = {
|
||||
workspace: Workspace
|
||||
excludedPlans?: ('STARTER' | 'PRO')[]
|
||||
}
|
||||
|
||||
export const ChangePlanForm = ({ workspace }: Props) => {
|
||||
export const ChangePlanForm = ({ workspace, excludedPlans }: Props) => {
|
||||
const scopedT = useScopedI18n('billing')
|
||||
|
||||
const { user } = useUser()
|
||||
@ -133,27 +134,31 @@ export const ChangePlanForm = ({ workspace }: Props) => {
|
||||
</HStack>
|
||||
</HStack>
|
||||
<HStack alignItems="stretch" spacing="4" w="full">
|
||||
<StarterPlanPricingCard
|
||||
workspace={workspace}
|
||||
currentSubscription={{ isYearly: data.subscription?.isYearly }}
|
||||
onPayClick={(props) =>
|
||||
handlePayClick({ ...props, plan: Plan.STARTER })
|
||||
}
|
||||
isYearly={isYearly}
|
||||
isLoading={isUpdatingSubscription}
|
||||
currency={data.subscription?.currency}
|
||||
/>
|
||||
{excludedPlans?.includes('STARTER') ? null : (
|
||||
<StarterPlanPricingCard
|
||||
workspace={workspace}
|
||||
currentSubscription={{ isYearly: data.subscription?.isYearly }}
|
||||
onPayClick={(props) =>
|
||||
handlePayClick({ ...props, plan: Plan.STARTER })
|
||||
}
|
||||
isYearly={isYearly}
|
||||
isLoading={isUpdatingSubscription}
|
||||
currency={data.subscription?.currency}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ProPlanPricingCard
|
||||
workspace={workspace}
|
||||
currentSubscription={{ isYearly: data.subscription?.isYearly }}
|
||||
onPayClick={(props) =>
|
||||
handlePayClick({ ...props, plan: Plan.PRO })
|
||||
}
|
||||
isYearly={isYearly}
|
||||
isLoading={isUpdatingSubscription}
|
||||
currency={data.subscription?.currency}
|
||||
/>
|
||||
{excludedPlans?.includes('PRO') ? null : (
|
||||
<ProPlanPricingCard
|
||||
workspace={workspace}
|
||||
currentSubscription={{ isYearly: data.subscription?.isYearly }}
|
||||
onPayClick={(props) =>
|
||||
handlePayClick({ ...props, plan: Plan.PRO })
|
||||
}
|
||||
isYearly={isYearly}
|
||||
isLoading={isUpdatingSubscription}
|
||||
currency={data.subscription?.currency}
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
</Stack>
|
||||
)}
|
||||
|
@ -13,9 +13,10 @@ import {
|
||||
} from '@chakra-ui/react'
|
||||
import { ChangePlanForm } from './ChangePlanForm'
|
||||
|
||||
type ChangePlanModalProps = {
|
||||
export type ChangePlanModalProps = {
|
||||
type?: string
|
||||
isOpen: boolean
|
||||
excludedPlans?: ('STARTER' | 'PRO')[]
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
@ -23,11 +24,16 @@ export const ChangePlanModal = ({
|
||||
onClose,
|
||||
isOpen,
|
||||
type,
|
||||
excludedPlans,
|
||||
}: ChangePlanModalProps) => {
|
||||
const t = useI18n()
|
||||
const { workspace } = useWorkspace()
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="2xl">
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
size={excludedPlans ? 'lg' : '2xl'}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalBody as={Stack} spacing="6" pt="10">
|
||||
@ -36,7 +42,12 @@ export const ChangePlanModal = ({
|
||||
{t('billing.upgradeLimitLabel', { type: type })}
|
||||
</AlertInfo>
|
||||
)}
|
||||
{workspace && <ChangePlanForm workspace={workspace} />}
|
||||
{workspace && (
|
||||
<ChangePlanForm
|
||||
workspace={workspace}
|
||||
excludedPlans={excludedPlans}
|
||||
/>
|
||||
)}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
|
@ -200,6 +200,7 @@ export const ProPlanPricingCard = ({
|
||||
</Text>
|
||||
<MoreInfoTooltip>{scopedT('chatsTooltip')}</MoreInfoTooltip>
|
||||
</HStack>,
|
||||
scopedT('pro.whatsAppIntegration'),
|
||||
scopedT('pro.customDomains'),
|
||||
scopedT('pro.analytics'),
|
||||
]}
|
||||
|
@ -5,9 +5,16 @@ import { isNotDefined } from '@typebot.io/lib'
|
||||
import { ChangePlanModal } from './ChangePlanModal'
|
||||
import { useI18n } from '@/locales'
|
||||
|
||||
type Props = { limitReachedType?: string } & ButtonProps
|
||||
type Props = {
|
||||
limitReachedType?: string
|
||||
excludedPlans?: ('STARTER' | 'PRO')[]
|
||||
} & ButtonProps
|
||||
|
||||
export const UpgradeButton = ({ limitReachedType, ...props }: Props) => {
|
||||
export const UpgradeButton = ({
|
||||
limitReachedType,
|
||||
excludedPlans,
|
||||
...props
|
||||
}: Props) => {
|
||||
const t = useI18n()
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const { workspace } = useWorkspace()
|
||||
@ -23,6 +30,7 @@ export const UpgradeButton = ({ limitReachedType, ...props }: Props) => {
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
type={limitReachedType}
|
||||
excludedPlans={excludedPlans}
|
||||
/>
|
||||
</Button>
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
import { Workspace, Plan } from '@typebot.io/prisma'
|
||||
|
||||
export const isProPlan = (workspace?: Pick<Workspace, 'plan'>) =>
|
||||
export const hasProPerks = (workspace?: Pick<Workspace, 'plan'>) =>
|
||||
isDefined(workspace) &&
|
||||
(workspace.plan === Plan.PRO ||
|
||||
workspace.plan === Plan.LIFETIME ||
|
@ -11,7 +11,7 @@ import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import React, { useMemo } from 'react'
|
||||
import { useEndpoints } from '../../providers/EndpointsProvider'
|
||||
import { useGroupsCoordinates } from '../../providers/GroupsCoordinateProvider'
|
||||
import { isProPlan } from '@/features/billing/helpers/isProPlan'
|
||||
import { hasProPerks } from '@/features/billing/helpers/hasProPerks'
|
||||
import { computeDropOffPath } from '../../helpers/computeDropOffPath'
|
||||
import { computeSourceCoordinates } from '../../helpers/computeSourceCoordinates'
|
||||
import { TotalAnswersInBlock } from '@typebot.io/schemas/features/analytics'
|
||||
@ -64,7 +64,7 @@ export const DropOffEdge = ({
|
||||
[blockId, totalAnswersInBlocks]
|
||||
)
|
||||
|
||||
const isWorkspaceProPlan = isProPlan(workspace)
|
||||
const isWorkspaceProPlan = hasProPerks(workspace)
|
||||
|
||||
const { totalDroppedUser, dropOffRate } = useMemo(() => {
|
||||
if (!publishedTypebot || currentBlock?.total === undefined)
|
||||
|
@ -20,7 +20,7 @@ import { integrationsList } from './embeds/EmbedButton'
|
||||
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
||||
import { LockTag } from '@/features/billing/components/LockTag'
|
||||
import { UpgradeButton } from '@/features/billing/components/UpgradeButton'
|
||||
import { isProPlan } from '@/features/billing/helpers/isProPlan'
|
||||
import { hasProPerks } from '@/features/billing/helpers/hasProPerks'
|
||||
import { CustomDomainsDropdown } from '@/features/customDomains/components/CustomDomainsDropdown'
|
||||
import { TypebotHeader } from '@/features/editor/components/TypebotHeader'
|
||||
import { parseDefaultPublicId } from '../helpers/parseDefaultPublicId'
|
||||
@ -130,7 +130,7 @@ export const SharePage = () => {
|
||||
{isNotDefined(typebot?.customDomain) &&
|
||||
env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME ? (
|
||||
<>
|
||||
{isProPlan(workspace) ? (
|
||||
{hasProPerks(workspace) ? (
|
||||
<CustomDomainsDropdown
|
||||
onCustomDomainSelect={handleCustomDomainChange}
|
||||
/>
|
||||
@ -138,6 +138,7 @@ export const SharePage = () => {
|
||||
<UpgradeButton
|
||||
colorScheme="gray"
|
||||
limitReachedType={t('billing.limitMessage.customDomain')}
|
||||
excludedPlans={[Plan.STARTER]}
|
||||
>
|
||||
<Text mr="2">Add my domain</Text>{' '}
|
||||
<LockTag plan={Plan.PRO} />
|
||||
|
@ -16,7 +16,6 @@ import {
|
||||
NotionLogo,
|
||||
WebflowLogo,
|
||||
IframeLogo,
|
||||
OtherLogo,
|
||||
} from './logos'
|
||||
import React from 'react'
|
||||
import {
|
||||
@ -30,7 +29,6 @@ import {
|
||||
IframeModal,
|
||||
WixModal,
|
||||
} from './modals'
|
||||
import { OtherModal } from './modals/OtherModal'
|
||||
import { ScriptModal } from './modals/Script/ScriptModal'
|
||||
import { CodeIcon } from '@/components/icons'
|
||||
import { ApiModal } from './modals/ApiModal'
|
||||
@ -43,6 +41,11 @@ import { WhatsAppLogo } from '@/components/logos/WhatsAppLogo'
|
||||
import { WhatsAppModal } from './modals/WhatsAppModal/WhatsAppModal'
|
||||
import { ParentModalProvider } from '@/features/graph/providers/ParentModalProvider'
|
||||
import { isWhatsAppAvailable } from '@/features/telemetry/posthog'
|
||||
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import { hasProPerks } from '@/features/billing/helpers/hasProPerks'
|
||||
import { ChangePlanModal } from '@/features/billing/components/ChangePlanModal'
|
||||
import { LockTag } from '@/features/billing/components/LockTag'
|
||||
import { Plan } from '@typebot.io/prisma'
|
||||
|
||||
export type ModalProps = {
|
||||
publicId: string
|
||||
@ -54,13 +57,15 @@ export type ModalProps = {
|
||||
type EmbedButtonProps = Pick<ModalProps, 'publicId' | 'isPublished'> & {
|
||||
logo: JSX.Element
|
||||
label: string
|
||||
Modal: (props: ModalProps) => JSX.Element
|
||||
lockTagPlan?: Plan
|
||||
modal: (modalProps: { onClose: () => void; isOpen: boolean }) => JSX.Element
|
||||
}
|
||||
|
||||
export const EmbedButton = ({
|
||||
logo,
|
||||
label,
|
||||
Modal,
|
||||
modal,
|
||||
lockTagPlan,
|
||||
...modalProps
|
||||
}: EmbedButtonProps) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
@ -75,22 +80,44 @@ export const EmbedButton = ({
|
||||
>
|
||||
<VStack>
|
||||
{logo}
|
||||
<Text>{label}</Text>
|
||||
<Text>
|
||||
{label}
|
||||
{lockTagPlan && (
|
||||
<>
|
||||
{' '}
|
||||
<LockTag plan={lockTagPlan} />
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
</VStack>
|
||||
<Modal isOpen={isOpen} onClose={onClose} {...modalProps} />
|
||||
{modal({ isOpen, onClose, ...modalProps })}
|
||||
</WrapItem>
|
||||
)
|
||||
}
|
||||
|
||||
export const integrationsList = [
|
||||
(props: Pick<ModalProps, 'publicId' | 'isPublished'>) => {
|
||||
const { workspace } = useWorkspace()
|
||||
|
||||
if (isWhatsAppAvailable())
|
||||
return (
|
||||
<ParentModalProvider>
|
||||
<EmbedButton
|
||||
logo={<WhatsAppLogo height={100} width="70px" />}
|
||||
label="WhatsApp"
|
||||
Modal={WhatsAppModal}
|
||||
lockTagPlan={hasProPerks(workspace) ? undefined : 'PRO'}
|
||||
modal={({ onClose, isOpen }) =>
|
||||
hasProPerks(workspace) ? (
|
||||
<WhatsAppModal isOpen={isOpen} onClose={onClose} {...props} />
|
||||
) : (
|
||||
<ChangePlanModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
excludedPlans={['STARTER']}
|
||||
type="deploy on WhatsApp"
|
||||
/>
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
</ParentModalProvider>
|
||||
@ -100,7 +127,9 @@ export const integrationsList = [
|
||||
<EmbedButton
|
||||
logo={<WordpressLogo height={100} width="70px" />}
|
||||
label="Wordpress"
|
||||
Modal={WordpressModal}
|
||||
modal={({ onClose, isOpen }) => (
|
||||
<WordpressModal isOpen={isOpen} onClose={onClose} {...props} />
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
@ -108,7 +137,7 @@ export const integrationsList = [
|
||||
<EmbedButton
|
||||
logo={<ShopifyLogo height={100} width="65px" />}
|
||||
label="Shopify"
|
||||
Modal={ShopifyModal}
|
||||
modal={(modalProps) => <ShopifyModal {...modalProps} {...props} />}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
@ -116,7 +145,7 @@ export const integrationsList = [
|
||||
<EmbedButton
|
||||
logo={<WixLogo height={100} width="90px" />}
|
||||
label="Wix"
|
||||
Modal={WixModal}
|
||||
modal={(modalProps) => <WixModal {...modalProps} {...props} />}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
@ -124,7 +153,7 @@ export const integrationsList = [
|
||||
<EmbedButton
|
||||
logo={<GtmLogo height={100} width="70px" />}
|
||||
label="Google Tag Manager"
|
||||
Modal={GtmModal}
|
||||
modal={(modalProps) => <GtmModal {...modalProps} {...props} />}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
@ -132,7 +161,7 @@ export const integrationsList = [
|
||||
<EmbedButton
|
||||
logo={<JavascriptLogo height={100} width="70px" />}
|
||||
label="HTML & Javascript"
|
||||
Modal={JavascriptModal}
|
||||
modal={(modalProps) => <JavascriptModal {...modalProps} {...props} />}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
@ -140,7 +169,7 @@ export const integrationsList = [
|
||||
<EmbedButton
|
||||
logo={<ReactLogo height={100} width="70px" />}
|
||||
label="React"
|
||||
Modal={ReactModal}
|
||||
modal={(modalProps) => <ReactModal {...modalProps} {...props} />}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
@ -148,7 +177,7 @@ export const integrationsList = [
|
||||
<EmbedButton
|
||||
logo={<NextjsLogo height={100} width="70px" />}
|
||||
label="Nextjs"
|
||||
Modal={NextjsModal}
|
||||
modal={(modalProps) => <NextjsModal {...modalProps} {...props} />}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
@ -156,7 +185,7 @@ export const integrationsList = [
|
||||
<EmbedButton
|
||||
logo={<CodeIcon height={100} width="60px" />}
|
||||
label="API"
|
||||
Modal={ApiModal}
|
||||
modal={(modalProps) => <ApiModal {...modalProps} {...props} />}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
@ -164,7 +193,7 @@ export const integrationsList = [
|
||||
<EmbedButton
|
||||
logo={<NotionLogo height={100} width="60px" />}
|
||||
label="Notion"
|
||||
Modal={NotionModal}
|
||||
modal={(modalProps) => <NotionModal {...modalProps} {...props} />}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
@ -172,7 +201,7 @@ export const integrationsList = [
|
||||
<EmbedButton
|
||||
logo={<WebflowLogo height={100} width="70px" />}
|
||||
label="Webflow"
|
||||
Modal={WebflowModal}
|
||||
modal={(modalProps) => <WebflowModal {...modalProps} {...props} />}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
@ -180,7 +209,7 @@ export const integrationsList = [
|
||||
<EmbedButton
|
||||
logo={<FlutterFlowLogo height={100} width="60px" />}
|
||||
label="FlutterFlow"
|
||||
Modal={FlutterFlowModal}
|
||||
modal={(modalProps) => <FlutterFlowModal {...modalProps} {...props} />}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
@ -194,7 +223,7 @@ export const integrationsList = [
|
||||
/>
|
||||
}
|
||||
label="Script"
|
||||
Modal={ScriptModal}
|
||||
modal={(modalProps) => <ScriptModal {...modalProps} {...props} />}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
@ -202,15 +231,7 @@ export const integrationsList = [
|
||||
<EmbedButton
|
||||
logo={<IframeLogo height={100} width="70px" />}
|
||||
label="Iframe"
|
||||
Modal={IframeModal}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
(props: Pick<ModalProps, 'publicId' | 'isPublished'>) => (
|
||||
<EmbedButton
|
||||
logo={<OtherLogo height={100} width="70px" />}
|
||||
label="Other"
|
||||
Modal={OtherModal}
|
||||
modal={(modalProps) => <IframeModal {...modalProps} {...props} />}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
|
@ -1,25 +0,0 @@
|
||||
import React, { useState } from 'react'
|
||||
import { isDefined } from '@udecode/plate-common'
|
||||
import { EmbedModal } from '../EmbedModal'
|
||||
import { JavascriptInstructions } from './Javascript/instructions/JavascriptInstructions'
|
||||
import { ModalProps } from '../EmbedButton'
|
||||
|
||||
export const OtherModal = ({ isOpen, onClose, isPublished }: ModalProps) => {
|
||||
const [selectedEmbedType, setSelectedEmbedType] = useState<
|
||||
'standard' | 'popup' | 'bubble' | undefined
|
||||
>()
|
||||
return (
|
||||
<EmbedModal
|
||||
titlePrefix="Other"
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
isPublished={isPublished}
|
||||
onSelectEmbedType={setSelectedEmbedType}
|
||||
selectedEmbedType={selectedEmbedType}
|
||||
>
|
||||
{isDefined(selectedEmbedType) && (
|
||||
<JavascriptInstructions type={selectedEmbedType} />
|
||||
)}
|
||||
</EmbedModal>
|
||||
)
|
||||
}
|
@ -23,6 +23,5 @@ export const convertPublicTypebotToTypebot = (
|
||||
isClosed: existingTypebot.isClosed,
|
||||
resultsTablePreferences: existingTypebot.resultsTablePreferences,
|
||||
selectedThemeTemplateId: existingTypebot.selectedThemeTemplateId,
|
||||
whatsAppPhoneNumberId: existingTypebot.whatsAppPhoneNumberId,
|
||||
whatsAppCredentialsId: existingTypebot.whatsAppCredentialsId,
|
||||
})
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
import { isWriteTypebotForbidden } from '../helpers/isWriteTypebotForbidden'
|
||||
import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
|
||||
import { Prisma } from '@typebot.io/prisma'
|
||||
import { hasProPerks } from '@/features/billing/helpers/hasProPerks'
|
||||
|
||||
export const updateTypebot = authenticatedProcedure
|
||||
.meta({
|
||||
@ -30,7 +31,6 @@ export const updateTypebot = authenticatedProcedure
|
||||
typebotSchema._def.schema
|
||||
.pick({
|
||||
isClosed: true,
|
||||
whatsAppPhoneNumberId: true,
|
||||
whatsAppCredentialsId: true,
|
||||
})
|
||||
.partial()
|
||||
@ -70,7 +70,6 @@ export const updateTypebot = authenticatedProcedure
|
||||
plan: true,
|
||||
},
|
||||
},
|
||||
whatsAppPhoneNumberId: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
})
|
||||
@ -119,6 +118,16 @@ export const updateTypebot = authenticatedProcedure
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
typebot.whatsAppCredentialsId &&
|
||||
!hasProPerks(existingTypebot.workspace)
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'WhatsApp is only available for Pro workspaces',
|
||||
})
|
||||
}
|
||||
|
||||
const newTypebot = await prisma.typebot.update({
|
||||
where: {
|
||||
id: existingTypebot.id,
|
||||
@ -151,7 +160,6 @@ export const updateTypebot = authenticatedProcedure
|
||||
customDomain:
|
||||
typebot.customDomain === null ? null : typebot.customDomain,
|
||||
isClosed: typebot.isClosed,
|
||||
whatsAppPhoneNumberId: typebot.whatsAppPhoneNumberId ?? undefined,
|
||||
whatsAppCredentialsId: typebot.whatsAppCredentialsId ?? undefined,
|
||||
},
|
||||
})
|
||||
|
@ -152,6 +152,7 @@ export default {
|
||||
'billing.pricingCard.pro.description': 'Für Agenturen & wachsende Start-ups.',
|
||||
'billing.pricingCard.pro.everythingFromStarter': 'Alles in Starter',
|
||||
'billing.pricingCard.pro.includedSeats': '5 Plätze inklusive',
|
||||
'billing.pricingCard.pro.whatsAppIntegration': 'WhatsApp-Integration',
|
||||
'billing.pricingCard.pro.customDomains': 'Eigene Domains',
|
||||
'billing.pricingCard.pro.analytics': 'Detaillierte Analysen',
|
||||
'billing.usage.heading': 'Nutzung',
|
||||
|
@ -147,6 +147,7 @@ export default {
|
||||
'billing.pricingCard.pro.description': 'For agencies & growing startups.',
|
||||
'billing.pricingCard.pro.everythingFromStarter': 'Everything in Starter',
|
||||
'billing.pricingCard.pro.includedSeats': '5 seats included',
|
||||
'billing.pricingCard.pro.whatsAppIntegration': 'WhatsApp integration',
|
||||
'billing.pricingCard.pro.customDomains': 'Custom domains',
|
||||
'billing.pricingCard.pro.analytics': 'In-depth analytics',
|
||||
'billing.usage.heading': 'Usage',
|
||||
|
@ -151,6 +151,7 @@ export default {
|
||||
'billing.pricingCard.pro.everythingFromStarter':
|
||||
"Tout ce qu'il y a dans Starter",
|
||||
'billing.pricingCard.pro.includedSeats': '5 collègues inclus',
|
||||
'billing.pricingCard.pro.whatsAppIntegration': 'Intégration WhatsApp',
|
||||
'billing.pricingCard.pro.customDomains': 'Domaines personnalisés',
|
||||
'billing.pricingCard.pro.analytics': 'Analyses approfondies',
|
||||
'billing.usage.heading': 'Utilisation',
|
||||
|
@ -154,6 +154,7 @@ export default {
|
||||
'Para agências e startups em crescimento.',
|
||||
'billing.pricingCard.pro.everythingFromStarter': 'Tudo em Starter',
|
||||
'billing.pricingCard.pro.includedSeats': '5 assentos incluídos',
|
||||
'billing.pricingCard.pro.whatsAppIntegration': 'Integração do WhatsApp',
|
||||
'billing.pricingCard.pro.customDomains': 'Domínios personalizados',
|
||||
'billing.pricingCard.pro.analytics': 'Análises aprofundadas',
|
||||
'billing.usage.heading': 'Uso',
|
||||
|
@ -155,6 +155,7 @@ export default {
|
||||
'Para agências e startups em crescimento.',
|
||||
'billing.pricingCard.pro.everythingFromStarter': 'Tudo em Starter',
|
||||
'billing.pricingCard.pro.includedSeats': '5 lugares incluídos',
|
||||
'billing.pricingCard.pro.whatsAppIntegration': 'Integração do WhatsApp',
|
||||
'billing.pricingCard.pro.customDomains': 'Domínios personalizados',
|
||||
'billing.pricingCard.pro.analytics': 'Análises aprofundadas',
|
||||
'billing.usage.heading': 'Uso',
|
||||
|
@ -191,6 +191,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
await prisma.typebot.updateMany({
|
||||
where: { id: typebot.id },
|
||||
data: {
|
||||
whatsAppCredentialsId: null,
|
||||
settings: {
|
||||
...settings,
|
||||
general: {
|
||||
|
@ -8687,6 +8687,12 @@
|
||||
"comparisons"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"sessionExpiryTimeout": {
|
||||
"type": "number",
|
||||
"maximum": 48,
|
||||
"minimum": 0.01,
|
||||
"description": "Expiration delay in hours after latest interaction"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -12799,6 +12805,12 @@
|
||||
"comparisons"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"sessionExpiryTimeout": {
|
||||
"type": "number",
|
||||
"maximum": 48,
|
||||
"minimum": 0.01,
|
||||
"description": "Expiration delay in hours after latest interaction"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -12874,10 +12886,6 @@
|
||||
"isClosed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"whatsAppPhoneNumberId": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"whatsAppCredentialsId": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
@ -12903,7 +12911,6 @@
|
||||
"resultsTablePreferences",
|
||||
"isArchived",
|
||||
"isClosed",
|
||||
"whatsAppPhoneNumberId",
|
||||
"whatsAppCredentialsId"
|
||||
],
|
||||
"additionalProperties": false
|
||||
@ -16878,6 +16885,12 @@
|
||||
"comparisons"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"sessionExpiryTimeout": {
|
||||
"type": "number",
|
||||
"maximum": 48,
|
||||
"minimum": 0.01,
|
||||
"description": "Expiration delay in hours after latest interaction"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -17020,10 +17033,6 @@
|
||||
"isClosed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"whatsAppPhoneNumberId": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"whatsAppCredentialsId": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
@ -21014,6 +21023,12 @@
|
||||
"comparisons"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"sessionExpiryTimeout": {
|
||||
"type": "number",
|
||||
"maximum": 48,
|
||||
"minimum": 0.01,
|
||||
"description": "Expiration delay in hours after latest interaction"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -21089,10 +21104,6 @@
|
||||
"isClosed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"whatsAppPhoneNumberId": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"whatsAppCredentialsId": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
@ -21118,7 +21129,6 @@
|
||||
"resultsTablePreferences",
|
||||
"isArchived",
|
||||
"isClosed",
|
||||
"whatsAppPhoneNumberId",
|
||||
"whatsAppCredentialsId"
|
||||
],
|
||||
"additionalProperties": false
|
||||
@ -25117,6 +25127,12 @@
|
||||
"comparisons"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"sessionExpiryTimeout": {
|
||||
"type": "number",
|
||||
"maximum": 48,
|
||||
"minimum": 0.01,
|
||||
"description": "Expiration delay in hours after latest interaction"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -25192,10 +25208,6 @@
|
||||
"isClosed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"whatsAppPhoneNumberId": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"whatsAppCredentialsId": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
@ -25221,7 +25233,6 @@
|
||||
"resultsTablePreferences",
|
||||
"isArchived",
|
||||
"isClosed",
|
||||
"whatsAppPhoneNumberId",
|
||||
"whatsAppCredentialsId"
|
||||
],
|
||||
"additionalProperties": false
|
||||
@ -29279,6 +29290,12 @@
|
||||
"comparisons"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"sessionExpiryTimeout": {
|
||||
"type": "number",
|
||||
"maximum": 48,
|
||||
"minimum": 0.01,
|
||||
"description": "Expiration delay in hours after latest interaction"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
@ -3815,6 +3815,12 @@
|
||||
"comparisons"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"sessionExpiryTimeout": {
|
||||
"type": "number",
|
||||
"maximum": 48,
|
||||
"minimum": 0.01,
|
||||
"description": "Expiration delay in hours after latest interaction"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -6226,6 +6232,12 @@
|
||||
"comparisons"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"sessionExpiryTimeout": {
|
||||
"type": "number",
|
||||
"maximum": 48,
|
||||
"minimum": 0.01,
|
||||
"description": "Expiration delay in hours after latest interaction"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
@ -85,6 +85,7 @@ export const ProPlanCard = ({ isYearly }: Props) => {
|
||||
</chakra.span>
|
||||
</Tooltip>
|
||||
</HStack>,
|
||||
'WhatsApp integration',
|
||||
'Custom domains',
|
||||
'In-depth analytics',
|
||||
],
|
||||
|
@ -20,7 +20,6 @@ import { validateUrl } from './blocks/inputs/url/validateUrl'
|
||||
import { resumeChatCompletion } from './blocks/integrations/openai/resumeChatCompletion'
|
||||
import { resumeWebhookExecution } from './blocks/integrations/webhook/resumeWebhookExecution'
|
||||
import { upsertAnswer } from './queries/upsertAnswer'
|
||||
import { startBotFlow } from './startBotFlow'
|
||||
import { parseButtonsReply } from './blocks/inputs/buttons/parseButtonsReply'
|
||||
import { ParsedReply } from './types'
|
||||
import { validateNumber } from './blocks/inputs/number/validateNumber'
|
||||
|
@ -75,7 +75,7 @@ export const resumeWhatsAppFlow = async ({
|
||||
? await continueBotFlow(sessionState)(messageContent)
|
||||
: workspaceId
|
||||
? await startWhatsAppSession({
|
||||
message: receivedMessage,
|
||||
incomingMessage: messageContent,
|
||||
sessionId,
|
||||
workspaceId,
|
||||
credentials: { ...credentials, id: credentialsId as string },
|
||||
|
@ -13,11 +13,14 @@ import {
|
||||
WhatsAppIncomingMessage,
|
||||
defaultSessionExpiryTimeout,
|
||||
} from '@typebot.io/schemas/features/whatsapp'
|
||||
import { isNotDefined } from '@typebot.io/lib/utils'
|
||||
import { isInputBlock, isNotDefined } from '@typebot.io/lib/utils'
|
||||
import { startSession } from '../startSession'
|
||||
import { getNextGroup } from '../getNextGroup'
|
||||
import { continueBotFlow } from '../continueBotFlow'
|
||||
import { upsertResult } from '../queries/upsertResult'
|
||||
|
||||
type Props = {
|
||||
message: WhatsAppIncomingMessage
|
||||
incomingMessage?: string
|
||||
sessionId: string
|
||||
workspaceId?: string
|
||||
credentials: WhatsAppCredentials['data'] & Pick<WhatsAppCredentials, 'id'>
|
||||
@ -25,7 +28,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const startWhatsAppSession = async ({
|
||||
message,
|
||||
incomingMessage,
|
||||
workspaceId,
|
||||
credentials,
|
||||
contact,
|
||||
@ -63,20 +66,41 @@ export const startWhatsAppSession = async ({
|
||||
(publicTypebot) =>
|
||||
publicTypebot.settings.whatsApp?.startCondition &&
|
||||
messageMatchStartCondition(
|
||||
getIncomingMessageText(message),
|
||||
incomingMessage ?? '',
|
||||
publicTypebot.settings.whatsApp?.startCondition
|
||||
)
|
||||
) ?? botsWithWhatsAppEnabled[0]
|
||||
|
||||
if (isNotDefined(publicTypebot)) return
|
||||
|
||||
const session = await startSession({
|
||||
let session = await startSession({
|
||||
startParams: {
|
||||
typebot: publicTypebot.typebot.publicId as string,
|
||||
},
|
||||
userId: undefined,
|
||||
})
|
||||
|
||||
// If first block is an input block, we can directly continue the bot flow
|
||||
const firstEdgeId =
|
||||
session.newSessionState.typebotsQueue[0].typebot.groups[0].blocks[0]
|
||||
.outgoingEdgeId
|
||||
const nextGroup = await getNextGroup(session.newSessionState)(firstEdgeId)
|
||||
const firstBlock = nextGroup.group?.blocks.at(0)
|
||||
if (firstBlock && isInputBlock(firstBlock)) {
|
||||
const resultId = session.newSessionState.typebotsQueue[0].resultId
|
||||
if (resultId)
|
||||
await upsertResult({
|
||||
hasStarted: true,
|
||||
isCompleted: false,
|
||||
resultId,
|
||||
typebot: session.newSessionState.typebotsQueue[0].typebot,
|
||||
})
|
||||
session = await continueBotFlow({
|
||||
...session.newSessionState,
|
||||
currentBlock: { groupId: firstBlock.groupId, blockId: firstBlock.id },
|
||||
})(incomingMessage)
|
||||
}
|
||||
|
||||
const sessionExpiryTimeoutHours =
|
||||
publicTypebot.settings.whatsApp?.sessionExpiryTimeout ??
|
||||
defaultSessionExpiryTimeout
|
||||
@ -166,21 +190,3 @@ const matchComparison = (
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getIncomingMessageText = (message: WhatsAppIncomingMessage): string => {
|
||||
switch (message.type) {
|
||||
case 'text':
|
||||
return message.text.body
|
||||
case 'button':
|
||||
return message.button.text
|
||||
case 'interactive': {
|
||||
return message.interactive.button_reply.title
|
||||
}
|
||||
case 'video':
|
||||
case 'document':
|
||||
case 'audio':
|
||||
case 'image': {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,6 @@ export const parseTestTypebot = (
|
||||
isArchived: false,
|
||||
isClosed: false,
|
||||
resultsTablePreferences: null,
|
||||
whatsAppPhoneNumberId: null,
|
||||
whatsAppCredentialsId: null,
|
||||
variables: [{ id: 'var1', name: 'var1' }],
|
||||
...partialTypebot,
|
||||
|
@ -198,7 +198,6 @@ model Typebot {
|
||||
webhooks Webhook[]
|
||||
isArchived Boolean @default(false)
|
||||
isClosed Boolean @default(false)
|
||||
whatsAppPhoneNumberId String?
|
||||
whatsAppCredentialsId String?
|
||||
|
||||
@@index([workspaceId])
|
||||
|
@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `whatsAppPhoneNumberId` on the `Typebot` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Typebot" DROP COLUMN "whatsAppPhoneNumberId";
|
@ -182,7 +182,6 @@ model Typebot {
|
||||
webhooks Webhook[]
|
||||
isArchived Boolean @default(false)
|
||||
isClosed Boolean @default(false)
|
||||
whatsAppPhoneNumberId String?
|
||||
whatsAppCredentialsId String?
|
||||
|
||||
@@index([workspaceId])
|
||||
|
@ -56,7 +56,6 @@ export const typebotSchema = z.preprocess(
|
||||
resultsTablePreferences: resultsTablePreferencesSchema.nullable(),
|
||||
isArchived: z.boolean(),
|
||||
isClosed: z.boolean(),
|
||||
whatsAppPhoneNumberId: z.string().nullable(),
|
||||
whatsAppCredentialsId: z.string().nullable(),
|
||||
}) satisfies z.ZodType<TypebotPrisma, z.ZodTypeDef, unknown>
|
||||
)
|
||||
|
Reference in New Issue
Block a user