feat(engine): ✨ Add {{state}} to body to get form state
This commit is contained in:
@ -62,6 +62,9 @@ export const PreviewDrawer = () => {
|
|||||||
if (event.data.typebotInfo) {
|
if (event.data.typebotInfo) {
|
||||||
toast({ description: event.data.typebotInfo })
|
toast({ description: event.data.typebotInfo })
|
||||||
}
|
}
|
||||||
|
if (event.data.typebotError) {
|
||||||
|
toast({ description: event.data.typebotError, status: 'error' })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
window.addEventListener('message', onMessageFromBot)
|
window.addEventListener('message', onMessageFromBot)
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import { Prisma } from 'db'
|
import { Prisma } from 'db'
|
||||||
import prisma from 'libs/prisma'
|
import prisma from 'libs/prisma'
|
||||||
import { IntegrationStepType, Typebot } from 'models'
|
import { HttpMethod, IntegrationStepType, Typebot } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { authenticateUser } from 'services/api/utils'
|
import { authenticateUser } from 'services/api/utils'
|
||||||
import { methodNotAllowed } from 'utils'
|
import { methodNotAllowed } from 'utils'
|
||||||
@ -47,7 +47,15 @@ const addUrlToWebhookStep = (
|
|||||||
steps: b.steps.map((s) => {
|
steps: b.steps.map((s) => {
|
||||||
if (s.id === stepId) {
|
if (s.id === stepId) {
|
||||||
if (s.type !== IntegrationStepType.WEBHOOK) throw new Error()
|
if (s.type !== IntegrationStepType.WEBHOOK) throw new Error()
|
||||||
return { ...s, webhook: { ...s.webhook, url } }
|
return {
|
||||||
|
...s,
|
||||||
|
webhook: {
|
||||||
|
...s.webhook,
|
||||||
|
url,
|
||||||
|
method: HttpMethod.POST,
|
||||||
|
body: '{{state}}',
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}),
|
}),
|
||||||
|
@ -16,6 +16,7 @@ import { executeLogic } from 'services/logic'
|
|||||||
import { executeIntegration } from 'services/integration'
|
import { executeIntegration } from 'services/integration'
|
||||||
import { parseRetryStep, stepCanBeRetried } from 'services/inputs'
|
import { parseRetryStep, stepCanBeRetried } from 'services/inputs'
|
||||||
import { parseVariables } from 'index'
|
import { parseVariables } from 'index'
|
||||||
|
import { useAnswers } from 'contexts/AnswersContext'
|
||||||
|
|
||||||
type ChatBlockProps = {
|
type ChatBlockProps = {
|
||||||
steps: PublicStep[]
|
steps: PublicStep[]
|
||||||
@ -32,6 +33,7 @@ export const ChatBlock = ({
|
|||||||
}: ChatBlockProps) => {
|
}: ChatBlockProps) => {
|
||||||
const { typebot, updateVariableValue, createEdge, apiHost, isPreview } =
|
const { typebot, updateVariableValue, createEdge, apiHost, isPreview } =
|
||||||
useTypebot()
|
useTypebot()
|
||||||
|
const { resultValues } = useAnswers()
|
||||||
const [displayedSteps, setDisplayedSteps] = useState<PublicStep[]>([])
|
const [displayedSteps, setDisplayedSteps] = useState<PublicStep[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -68,6 +70,8 @@ export const ChatBlock = ({
|
|||||||
variables: typebot.variables,
|
variables: typebot.variables,
|
||||||
isPreview,
|
isPreview,
|
||||||
updateVariableValue,
|
updateVariableValue,
|
||||||
|
resultValues,
|
||||||
|
blocks: typebot.blocks,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
nextEdgeId ? onBlockEnd(nextEdgeId) : displayNextStep()
|
nextEdgeId ? onBlockEnd(nextEdgeId) : displayNextStep()
|
||||||
|
@ -30,7 +30,10 @@ export const ConversationContainer = ({
|
|||||||
{ block: PublicBlock; startStepIndex: number }[]
|
{ block: PublicBlock; startStepIndex: number }[]
|
||||||
>([])
|
>([])
|
||||||
const [localAnswer, setLocalAnswer] = useState<Answer | undefined>()
|
const [localAnswer, setLocalAnswer] = useState<Answer | undefined>()
|
||||||
const { answers } = useAnswers()
|
const {
|
||||||
|
resultValues: { answers },
|
||||||
|
setPrefilledVariables,
|
||||||
|
} = useAnswers()
|
||||||
const bottomAnchor = useRef<HTMLDivElement | null>(null)
|
const bottomAnchor = useRef<HTMLDivElement | null>(null)
|
||||||
const scrollableContainer = useRef<HTMLDivElement | null>(null)
|
const scrollableContainer = useRef<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
@ -51,7 +54,10 @@ export const ConversationContainer = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const prefilledVariables = injectUrlParamsIntoVariables()
|
const prefilledVariables = injectUrlParamsIntoVariables()
|
||||||
if (onVariablesPrefilled) onVariablesPrefilled(prefilledVariables)
|
if (onVariablesPrefilled) {
|
||||||
|
onVariablesPrefilled(prefilledVariables)
|
||||||
|
setPrefilledVariables(prefilledVariables)
|
||||||
|
}
|
||||||
displayNextBlock(typebot.blocks[0].steps[0].outgoingEdgeId)
|
displayNextBlock(typebot.blocks[0].steps[0].outgoingEdgeId)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
@ -1,24 +1,43 @@
|
|||||||
import { Answer } from 'models'
|
import { Answer, ResultWithAnswers, VariableWithValue } from 'models'
|
||||||
import React, { createContext, ReactNode, useContext, useState } from 'react'
|
import React, { createContext, ReactNode, useContext, useState } from 'react'
|
||||||
|
|
||||||
|
export type ResultValues = Pick<
|
||||||
|
ResultWithAnswers,
|
||||||
|
'answers' | 'createdAt' | 'prefilledVariables'
|
||||||
|
>
|
||||||
const answersContext = createContext<{
|
const answersContext = createContext<{
|
||||||
answers: Answer[]
|
resultValues: ResultValues
|
||||||
addAnswer: (answer: Answer) => void
|
addAnswer: (answer: Answer) => void
|
||||||
|
setPrefilledVariables: (variables: VariableWithValue[]) => void
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
export const AnswersContext = ({ children }: { children: ReactNode }) => {
|
export const AnswersContext = ({ children }: { children: ReactNode }) => {
|
||||||
const [answers, setAnswers] = useState<Answer[]>([])
|
const [resultValues, setResultValues] = useState<ResultValues>({
|
||||||
|
answers: [],
|
||||||
|
prefilledVariables: [],
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
|
||||||
const addAnswer = (answer: Answer) =>
|
const addAnswer = (answer: Answer) =>
|
||||||
setAnswers((answers) => [...answers, answer])
|
setResultValues((resultValues) => ({
|
||||||
|
...resultValues,
|
||||||
|
answers: [...resultValues.answers, answer],
|
||||||
|
}))
|
||||||
|
|
||||||
|
const setPrefilledVariables = (variables: VariableWithValue[]) =>
|
||||||
|
setResultValues((resultValues) => ({
|
||||||
|
...resultValues,
|
||||||
|
prefilledVariables: variables,
|
||||||
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<answersContext.Provider
|
<answersContext.Provider
|
||||||
value={{
|
value={{
|
||||||
answers,
|
resultValues,
|
||||||
addAnswer,
|
addAnswer,
|
||||||
|
setPrefilledVariables,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ResultValues } from 'contexts/AnswersContext'
|
||||||
import {
|
import {
|
||||||
IntegrationStep,
|
IntegrationStep,
|
||||||
IntegrationStepType,
|
IntegrationStepType,
|
||||||
@ -11,11 +12,12 @@ import {
|
|||||||
GoogleAnalyticsStep,
|
GoogleAnalyticsStep,
|
||||||
WebhookStep,
|
WebhookStep,
|
||||||
SendEmailStep,
|
SendEmailStep,
|
||||||
|
PublicBlock,
|
||||||
} from 'models'
|
} from 'models'
|
||||||
import { stringify } from 'qs'
|
import { stringify } from 'qs'
|
||||||
import { sendRequest } from 'utils'
|
import { parseAnswers, sendRequest } from 'utils'
|
||||||
import { sendGaEvent } from '../../lib/gtag'
|
import { sendGaEvent } from '../../lib/gtag'
|
||||||
import { sendInfoMessage } from './postMessage'
|
import { sendErrorMessage, sendInfoMessage } from './postMessage'
|
||||||
import { parseVariables, parseVariablesInObject } from './variable'
|
import { parseVariables, parseVariablesInObject } from './variable'
|
||||||
|
|
||||||
const safeEval = eval
|
const safeEval = eval
|
||||||
@ -27,8 +29,11 @@ type IntegrationContext = {
|
|||||||
stepId: string
|
stepId: string
|
||||||
isPreview: boolean
|
isPreview: boolean
|
||||||
variables: Variable[]
|
variables: Variable[]
|
||||||
|
resultValues: ResultValues
|
||||||
|
blocks: PublicBlock[]
|
||||||
updateVariableValue: (variableId: string, value: string) => void
|
updateVariableValue: (variableId: string, value: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const executeIntegration = ({
|
export const executeIntegration = ({
|
||||||
step,
|
step,
|
||||||
context,
|
context,
|
||||||
@ -179,7 +184,7 @@ const executeWebhook = async (
|
|||||||
|
|
||||||
const sendEmail = async (
|
const sendEmail = async (
|
||||||
step: SendEmailStep,
|
step: SendEmailStep,
|
||||||
{ variables, apiHost, isPreview }: IntegrationContext
|
{ variables, apiHost, isPreview, resultValues, blocks }: IntegrationContext
|
||||||
) => {
|
) => {
|
||||||
if (isPreview) sendInfoMessage('Emails are not sent in preview mode')
|
if (isPreview) sendInfoMessage('Emails are not sent in preview mode')
|
||||||
if (isPreview) return step.outgoingEdgeId
|
if (isPreview) return step.outgoingEdgeId
|
||||||
@ -191,11 +196,15 @@ const sendEmail = async (
|
|||||||
credentialsId: options.credentialsId,
|
credentialsId: options.credentialsId,
|
||||||
recipients: options.recipients.map(parseVariables(variables)),
|
recipients: options.recipients.map(parseVariables(variables)),
|
||||||
subject: parseVariables(variables)(options.subject ?? ''),
|
subject: parseVariables(variables)(options.subject ?? ''),
|
||||||
body: parseVariables(variables)(options.body ?? ''),
|
body:
|
||||||
|
options.body === '{{state}}'
|
||||||
|
? parseAnswers({ variables, blocks })(resultValues)
|
||||||
|
: parseVariables(variables)(options.body ?? ''),
|
||||||
cc: (options.cc ?? []).map(parseVariables(variables)),
|
cc: (options.cc ?? []).map(parseVariables(variables)),
|
||||||
bcc: (options.bcc ?? []).map(parseVariables(variables)),
|
bcc: (options.bcc ?? []).map(parseVariables(variables)),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
if (isPreview && error) sendErrorMessage(`Webhook failed: ${error.message}`)
|
||||||
return step.outgoingEdgeId
|
return step.outgoingEdgeId
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
export const sendInfoMessage = (typebotInfo: string) => {
|
export const sendInfoMessage = (typebotInfo: string) => {
|
||||||
parent.postMessage({ typebotInfo })
|
parent.postMessage({ typebotInfo })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const sendErrorMessage = (typebotError: string) => {
|
||||||
|
parent.postMessage({ typebotError })
|
||||||
|
}
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
import { Typebot, Answer, VariableWithValue, ResultWithAnswers } from 'models'
|
import {
|
||||||
|
Typebot,
|
||||||
|
Answer,
|
||||||
|
VariableWithValue,
|
||||||
|
ResultWithAnswers,
|
||||||
|
PublicTypebot,
|
||||||
|
Block,
|
||||||
|
} from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { byId, isDefined } from '.'
|
import { byId, isDefined } from '.'
|
||||||
|
|
||||||
@ -24,17 +31,27 @@ export const initMiddleware =
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const parseAnswers =
|
export const parseAnswers =
|
||||||
({ blocks, variables }: Pick<Typebot, 'blocks' | 'variables'>) =>
|
({
|
||||||
(result: ResultWithAnswers) => ({
|
blocks,
|
||||||
submittedAt: result.createdAt,
|
variables,
|
||||||
...[...result.answers, ...result.prefilledVariables].reduce<{
|
}: Pick<Typebot | PublicTypebot, 'blocks' | 'variables'>) =>
|
||||||
|
({
|
||||||
|
createdAt,
|
||||||
|
answers,
|
||||||
|
prefilledVariables,
|
||||||
|
}: Pick<
|
||||||
|
ResultWithAnswers,
|
||||||
|
'createdAt' | 'answers' | 'prefilledVariables'
|
||||||
|
>) => ({
|
||||||
|
submittedAt: createdAt,
|
||||||
|
...[...answers, ...prefilledVariables].reduce<{
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
}>((o, answerOrVariable) => {
|
}>((o, answerOrVariable) => {
|
||||||
if ('blockId' in answerOrVariable) {
|
if ('blockId' in answerOrVariable) {
|
||||||
const answer = answerOrVariable as Answer
|
const answer = answerOrVariable as Answer
|
||||||
const key = answer.variableId
|
const key = answer.variableId
|
||||||
? variables.find(byId(answer.variableId))?.name
|
? variables.find(byId(answer.variableId))?.name
|
||||||
: blocks.find(byId(answer.blockId))?.title
|
: (blocks as Block[]).find(byId(answer.blockId))?.title
|
||||||
if (!key) return o
|
if (!key) return o
|
||||||
return {
|
return {
|
||||||
...o,
|
...o,
|
||||||
|
Reference in New Issue
Block a user