From b81fcf0167e332d6559e44ce409c44ce87316f55 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Mon, 25 Sep 2023 17:20:42 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Auto=20continue=20bot=20on=20whatsA?= =?UTF-8?q?pp=20if=20starting=20block=20is=20input=20=20(#849)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### 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. --- .../src/components/UnlockPlanAlertInfo.tsx | 17 +++- .../components/AnalyticsGraphContainer.tsx | 1 + .../billing/components/ChangePlanForm.tsx | 47 ++++++----- .../billing/components/ChangePlanModal.tsx | 17 +++- .../billing/components/ProPlanPricingCard.tsx | 1 + .../billing/components/UpgradeButton.tsx | 12 ++- .../helpers/{isProPlan.ts => hasProPerks.ts} | 2 +- .../graph/components/edges/DropOffEdge.tsx | 4 +- .../features/publish/components/SharePage.tsx | 5 +- .../publish/components/embeds/EmbedButton.tsx | 77 ++++++++++++------- .../components/embeds/modals/OtherModal.tsx | 25 ------ .../helpers/convertPublicTypebotToTypebot.ts | 1 - .../src/features/typebot/api/updateTypebot.ts | 14 +++- apps/builder/src/locales/de.ts | 1 + apps/builder/src/locales/en.ts | 1 + apps/builder/src/locales/fr.ts | 1 + apps/builder/src/locales/pt-BR.ts | 1 + apps/builder/src/locales/pt.ts | 1 + apps/builder/src/pages/api/stripe/webhook.ts | 1 + apps/docs/openapi/builder/_spec_.json | 55 ++++++++----- apps/docs/openapi/chat/_spec_.json | 12 +++ .../components/PricingPage/ProPlanCard.tsx | 1 + packages/bot-engine/continueBotFlow.ts | 1 - .../bot-engine/whatsapp/resumeWhatsAppFlow.ts | 2 +- .../whatsapp/startWhatsAppSession.ts | 52 +++++++------ packages/lib/playwright/databaseHelpers.ts | 1 - packages/prisma/mysql/schema.prisma | 1 - .../migration.sql | 8 ++ packages/prisma/postgresql/schema.prisma | 1 - packages/schemas/features/typebot/typebot.ts | 1 - 30 files changed, 224 insertions(+), 140 deletions(-) rename apps/builder/src/features/billing/helpers/{isProPlan.ts => hasProPerks.ts} (80%) delete mode 100644 apps/builder/src/features/publish/components/embeds/modals/OtherModal.tsx create mode 100644 packages/prisma/postgresql/migrations/20230925135118_remove_whatsapp_phone_number_id_field/migration.sql diff --git a/apps/builder/src/components/UnlockPlanAlertInfo.tsx b/apps/builder/src/components/UnlockPlanAlertInfo.tsx index 0ae8f3584..c73d41025 100644 --- a/apps/builder/src/components/UnlockPlanAlertInfo.tsx +++ b/apps/builder/src/components/UnlockPlanAlertInfo.tsx @@ -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 export const UnlockPlanAlertInfo = ({ contentLabel, buttonLabel, type, + excludedPlans, ...props }: Props) => { const t = useI18n() @@ -45,7 +49,12 @@ export const UnlockPlanAlertInfo = ({ > {buttonLabel ?? t('billing.upgradeAlert.buttonDefaultLabel')} - + ) } diff --git a/apps/builder/src/features/analytics/components/AnalyticsGraphContainer.tsx b/apps/builder/src/features/analytics/components/AnalyticsGraphContainer.tsx index 3527603ca..4cc89627d 100644 --- a/apps/builder/src/features/analytics/components/AnalyticsGraphContainer.tsx +++ b/apps/builder/src/features/analytics/components/AnalyticsGraphContainer.tsx @@ -82,6 +82,7 @@ export const AnalyticsGraphContainer = ({ stats }: { stats?: Stats }) => { onClose={onClose} isOpen={isOpen} type={t('billing.limitMessage.analytics')} + excludedPlans={['STARTER']} /> diff --git a/apps/builder/src/features/billing/components/ChangePlanForm.tsx b/apps/builder/src/features/billing/components/ChangePlanForm.tsx index d39521e06..5a1f88586 100644 --- a/apps/builder/src/features/billing/components/ChangePlanForm.tsx +++ b/apps/builder/src/features/billing/components/ChangePlanForm.tsx @@ -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) => { - - handlePayClick({ ...props, plan: Plan.STARTER }) - } - isYearly={isYearly} - isLoading={isUpdatingSubscription} - currency={data.subscription?.currency} - /> + {excludedPlans?.includes('STARTER') ? null : ( + + handlePayClick({ ...props, plan: Plan.STARTER }) + } + isYearly={isYearly} + isLoading={isUpdatingSubscription} + currency={data.subscription?.currency} + /> + )} - - handlePayClick({ ...props, plan: Plan.PRO }) - } - isYearly={isYearly} - isLoading={isUpdatingSubscription} - currency={data.subscription?.currency} - /> + {excludedPlans?.includes('PRO') ? null : ( + + handlePayClick({ ...props, plan: Plan.PRO }) + } + isYearly={isYearly} + isLoading={isUpdatingSubscription} + currency={data.subscription?.currency} + /> + )} )} diff --git a/apps/builder/src/features/billing/components/ChangePlanModal.tsx b/apps/builder/src/features/billing/components/ChangePlanModal.tsx index 70a36d9c7..9bf467ea9 100644 --- a/apps/builder/src/features/billing/components/ChangePlanModal.tsx +++ b/apps/builder/src/features/billing/components/ChangePlanModal.tsx @@ -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 ( - + @@ -36,7 +42,12 @@ export const ChangePlanModal = ({ {t('billing.upgradeLimitLabel', { type: type })} )} - {workspace && } + {workspace && ( + + )} diff --git a/apps/builder/src/features/billing/components/ProPlanPricingCard.tsx b/apps/builder/src/features/billing/components/ProPlanPricingCard.tsx index 1fca6e767..1a1516f2b 100644 --- a/apps/builder/src/features/billing/components/ProPlanPricingCard.tsx +++ b/apps/builder/src/features/billing/components/ProPlanPricingCard.tsx @@ -200,6 +200,7 @@ export const ProPlanPricingCard = ({ {scopedT('chatsTooltip')} , + scopedT('pro.whatsAppIntegration'), scopedT('pro.customDomains'), scopedT('pro.analytics'), ]} diff --git a/apps/builder/src/features/billing/components/UpgradeButton.tsx b/apps/builder/src/features/billing/components/UpgradeButton.tsx index 385eb9c91..b48b9bd2f 100644 --- a/apps/builder/src/features/billing/components/UpgradeButton.tsx +++ b/apps/builder/src/features/billing/components/UpgradeButton.tsx @@ -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} /> ) diff --git a/apps/builder/src/features/billing/helpers/isProPlan.ts b/apps/builder/src/features/billing/helpers/hasProPerks.ts similarity index 80% rename from apps/builder/src/features/billing/helpers/isProPlan.ts rename to apps/builder/src/features/billing/helpers/hasProPerks.ts index 6752191a0..a0ed05b3d 100644 --- a/apps/builder/src/features/billing/helpers/isProPlan.ts +++ b/apps/builder/src/features/billing/helpers/hasProPerks.ts @@ -1,7 +1,7 @@ import { isDefined } from '@typebot.io/lib' import { Workspace, Plan } from '@typebot.io/prisma' -export const isProPlan = (workspace?: Pick) => +export const hasProPerks = (workspace?: Pick) => isDefined(workspace) && (workspace.plan === Plan.PRO || workspace.plan === Plan.LIFETIME || diff --git a/apps/builder/src/features/graph/components/edges/DropOffEdge.tsx b/apps/builder/src/features/graph/components/edges/DropOffEdge.tsx index 7a8f2cd1c..c961bbb96 100644 --- a/apps/builder/src/features/graph/components/edges/DropOffEdge.tsx +++ b/apps/builder/src/features/graph/components/edges/DropOffEdge.tsx @@ -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) diff --git a/apps/builder/src/features/publish/components/SharePage.tsx b/apps/builder/src/features/publish/components/SharePage.tsx index c28821ca5..f3dd57d9d 100644 --- a/apps/builder/src/features/publish/components/SharePage.tsx +++ b/apps/builder/src/features/publish/components/SharePage.tsx @@ -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) ? ( @@ -138,6 +138,7 @@ export const SharePage = () => { Add my domain{' '} diff --git a/apps/builder/src/features/publish/components/embeds/EmbedButton.tsx b/apps/builder/src/features/publish/components/embeds/EmbedButton.tsx index 7bd359dd0..5e0160751 100644 --- a/apps/builder/src/features/publish/components/embeds/EmbedButton.tsx +++ b/apps/builder/src/features/publish/components/embeds/EmbedButton.tsx @@ -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 & { 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 = ({ > {logo} - {label} + + {label} + {lockTagPlan && ( + <> + {' '} + + + )} + - + {modal({ isOpen, onClose, ...modalProps })} ) } export const integrationsList = [ (props: Pick) => { + const { workspace } = useWorkspace() + if (isWhatsAppAvailable()) return ( } label="WhatsApp" - Modal={WhatsAppModal} + lockTagPlan={hasProPerks(workspace) ? undefined : 'PRO'} + modal={({ onClose, isOpen }) => + hasProPerks(workspace) ? ( + + ) : ( + + ) + } {...props} /> @@ -100,7 +127,9 @@ export const integrationsList = [ } label="Wordpress" - Modal={WordpressModal} + modal={({ onClose, isOpen }) => ( + + )} {...props} /> ), @@ -108,7 +137,7 @@ export const integrationsList = [ } label="Shopify" - Modal={ShopifyModal} + modal={(modalProps) => } {...props} /> ), @@ -116,7 +145,7 @@ export const integrationsList = [ } label="Wix" - Modal={WixModal} + modal={(modalProps) => } {...props} /> ), @@ -124,7 +153,7 @@ export const integrationsList = [ } label="Google Tag Manager" - Modal={GtmModal} + modal={(modalProps) => } {...props} /> ), @@ -132,7 +161,7 @@ export const integrationsList = [ } label="HTML & Javascript" - Modal={JavascriptModal} + modal={(modalProps) => } {...props} /> ), @@ -140,7 +169,7 @@ export const integrationsList = [ } label="React" - Modal={ReactModal} + modal={(modalProps) => } {...props} /> ), @@ -148,7 +177,7 @@ export const integrationsList = [ } label="Nextjs" - Modal={NextjsModal} + modal={(modalProps) => } {...props} /> ), @@ -156,7 +185,7 @@ export const integrationsList = [ } label="API" - Modal={ApiModal} + modal={(modalProps) => } {...props} /> ), @@ -164,7 +193,7 @@ export const integrationsList = [ } label="Notion" - Modal={NotionModal} + modal={(modalProps) => } {...props} /> ), @@ -172,7 +201,7 @@ export const integrationsList = [ } label="Webflow" - Modal={WebflowModal} + modal={(modalProps) => } {...props} /> ), @@ -180,7 +209,7 @@ export const integrationsList = [ } label="FlutterFlow" - Modal={FlutterFlowModal} + modal={(modalProps) => } {...props} /> ), @@ -194,7 +223,7 @@ export const integrationsList = [ /> } label="Script" - Modal={ScriptModal} + modal={(modalProps) => } {...props} /> ), @@ -202,15 +231,7 @@ export const integrationsList = [ } label="Iframe" - Modal={IframeModal} - {...props} - /> - ), - (props: Pick) => ( - } - label="Other" - Modal={OtherModal} + modal={(modalProps) => } {...props} /> ), diff --git a/apps/builder/src/features/publish/components/embeds/modals/OtherModal.tsx b/apps/builder/src/features/publish/components/embeds/modals/OtherModal.tsx deleted file mode 100644 index 04aa663cf..000000000 --- a/apps/builder/src/features/publish/components/embeds/modals/OtherModal.tsx +++ /dev/null @@ -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 ( - - {isDefined(selectedEmbedType) && ( - - )} - - ) -} diff --git a/apps/builder/src/features/publish/helpers/convertPublicTypebotToTypebot.ts b/apps/builder/src/features/publish/helpers/convertPublicTypebotToTypebot.ts index 7aded01fd..84eb74389 100644 --- a/apps/builder/src/features/publish/helpers/convertPublicTypebotToTypebot.ts +++ b/apps/builder/src/features/publish/helpers/convertPublicTypebotToTypebot.ts @@ -23,6 +23,5 @@ export const convertPublicTypebotToTypebot = ( isClosed: existingTypebot.isClosed, resultsTablePreferences: existingTypebot.resultsTablePreferences, selectedThemeTemplateId: existingTypebot.selectedThemeTemplateId, - whatsAppPhoneNumberId: existingTypebot.whatsAppPhoneNumberId, whatsAppCredentialsId: existingTypebot.whatsAppCredentialsId, }) diff --git a/apps/builder/src/features/typebot/api/updateTypebot.ts b/apps/builder/src/features/typebot/api/updateTypebot.ts index f41f21e3a..b4fa39dfc 100644 --- a/apps/builder/src/features/typebot/api/updateTypebot.ts +++ b/apps/builder/src/features/typebot/api/updateTypebot.ts @@ -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, }, }) diff --git a/apps/builder/src/locales/de.ts b/apps/builder/src/locales/de.ts index 548975c97..8a309a5f8 100644 --- a/apps/builder/src/locales/de.ts +++ b/apps/builder/src/locales/de.ts @@ -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', diff --git a/apps/builder/src/locales/en.ts b/apps/builder/src/locales/en.ts index 71c7fc394..fe351ae93 100644 --- a/apps/builder/src/locales/en.ts +++ b/apps/builder/src/locales/en.ts @@ -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', diff --git a/apps/builder/src/locales/fr.ts b/apps/builder/src/locales/fr.ts index 9bdbe54f5..28a7ee9a9 100644 --- a/apps/builder/src/locales/fr.ts +++ b/apps/builder/src/locales/fr.ts @@ -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', diff --git a/apps/builder/src/locales/pt-BR.ts b/apps/builder/src/locales/pt-BR.ts index e479882d4..7c3adb533 100644 --- a/apps/builder/src/locales/pt-BR.ts +++ b/apps/builder/src/locales/pt-BR.ts @@ -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', diff --git a/apps/builder/src/locales/pt.ts b/apps/builder/src/locales/pt.ts index 9c7663db2..6e502dcd1 100644 --- a/apps/builder/src/locales/pt.ts +++ b/apps/builder/src/locales/pt.ts @@ -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', diff --git a/apps/builder/src/pages/api/stripe/webhook.ts b/apps/builder/src/pages/api/stripe/webhook.ts index 0072c406b..290aa3bf0 100644 --- a/apps/builder/src/pages/api/stripe/webhook.ts +++ b/apps/builder/src/pages/api/stripe/webhook.ts @@ -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: { diff --git a/apps/docs/openapi/builder/_spec_.json b/apps/docs/openapi/builder/_spec_.json index 2f38237be..ef15c9c97 100644 --- a/apps/docs/openapi/builder/_spec_.json +++ b/apps/docs/openapi/builder/_spec_.json @@ -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 diff --git a/apps/docs/openapi/chat/_spec_.json b/apps/docs/openapi/chat/_spec_.json index b7938b708..d5b8d9c3b 100644 --- a/apps/docs/openapi/chat/_spec_.json +++ b/apps/docs/openapi/chat/_spec_.json @@ -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 diff --git a/apps/landing-page/components/PricingPage/ProPlanCard.tsx b/apps/landing-page/components/PricingPage/ProPlanCard.tsx index 3f95a337f..ed194f6fc 100644 --- a/apps/landing-page/components/PricingPage/ProPlanCard.tsx +++ b/apps/landing-page/components/PricingPage/ProPlanCard.tsx @@ -85,6 +85,7 @@ export const ProPlanCard = ({ isYearly }: Props) => { , + 'WhatsApp integration', 'Custom domains', 'In-depth analytics', ], diff --git a/packages/bot-engine/continueBotFlow.ts b/packages/bot-engine/continueBotFlow.ts index 015affb06..fd2d92b66 100644 --- a/packages/bot-engine/continueBotFlow.ts +++ b/packages/bot-engine/continueBotFlow.ts @@ -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' diff --git a/packages/bot-engine/whatsapp/resumeWhatsAppFlow.ts b/packages/bot-engine/whatsapp/resumeWhatsAppFlow.ts index a25f25785..26ca28204 100644 --- a/packages/bot-engine/whatsapp/resumeWhatsAppFlow.ts +++ b/packages/bot-engine/whatsapp/resumeWhatsAppFlow.ts @@ -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 }, diff --git a/packages/bot-engine/whatsapp/startWhatsAppSession.ts b/packages/bot-engine/whatsapp/startWhatsAppSession.ts index 4656e321f..96b64699d 100644 --- a/packages/bot-engine/whatsapp/startWhatsAppSession.ts +++ b/packages/bot-engine/whatsapp/startWhatsAppSession.ts @@ -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 @@ -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 '' - } - } -} diff --git a/packages/lib/playwright/databaseHelpers.ts b/packages/lib/playwright/databaseHelpers.ts index 240278e08..4abb2d8ca 100644 --- a/packages/lib/playwright/databaseHelpers.ts +++ b/packages/lib/playwright/databaseHelpers.ts @@ -31,7 +31,6 @@ export const parseTestTypebot = ( isArchived: false, isClosed: false, resultsTablePreferences: null, - whatsAppPhoneNumberId: null, whatsAppCredentialsId: null, variables: [{ id: 'var1', name: 'var1' }], ...partialTypebot, diff --git a/packages/prisma/mysql/schema.prisma b/packages/prisma/mysql/schema.prisma index 9d0513ae2..1023617af 100644 --- a/packages/prisma/mysql/schema.prisma +++ b/packages/prisma/mysql/schema.prisma @@ -198,7 +198,6 @@ model Typebot { webhooks Webhook[] isArchived Boolean @default(false) isClosed Boolean @default(false) - whatsAppPhoneNumberId String? whatsAppCredentialsId String? @@index([workspaceId]) diff --git a/packages/prisma/postgresql/migrations/20230925135118_remove_whatsapp_phone_number_id_field/migration.sql b/packages/prisma/postgresql/migrations/20230925135118_remove_whatsapp_phone_number_id_field/migration.sql new file mode 100644 index 000000000..cced7384d --- /dev/null +++ b/packages/prisma/postgresql/migrations/20230925135118_remove_whatsapp_phone_number_id_field/migration.sql @@ -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"; diff --git a/packages/prisma/postgresql/schema.prisma b/packages/prisma/postgresql/schema.prisma index e6404645c..65a73877d 100644 --- a/packages/prisma/postgresql/schema.prisma +++ b/packages/prisma/postgresql/schema.prisma @@ -182,7 +182,6 @@ model Typebot { webhooks Webhook[] isArchived Boolean @default(false) isClosed Boolean @default(false) - whatsAppPhoneNumberId String? whatsAppCredentialsId String? @@index([workspaceId]) diff --git a/packages/schemas/features/typebot/typebot.ts b/packages/schemas/features/typebot/typebot.ts index 70f8c41e6..ae684b594 100644 --- a/packages/schemas/features/typebot/typebot.ts +++ b/packages/schemas/features/typebot/typebot.ts @@ -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 )