feat(logic): ✨ Add Redirect step
This commit is contained in:
@@ -259,3 +259,11 @@ export const ExpandIcon = (props: IconProps) => (
|
|||||||
<line x1="3" y1="21" x2="10" y2="14"></line>
|
<line x1="3" y1="21" x2="10" y2="14"></line>
|
||||||
</Icon>
|
</Icon>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const ExternalLinkIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
||||||
|
<polyline points="15 3 21 3 21 9"></polyline>
|
||||||
|
<line x1="10" y1="14" x2="21" y2="3"></line>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
CheckSquareIcon,
|
CheckSquareIcon,
|
||||||
EditIcon,
|
EditIcon,
|
||||||
EmailIcon,
|
EmailIcon,
|
||||||
|
ExternalLinkIcon,
|
||||||
FilterIcon,
|
FilterIcon,
|
||||||
FlagIcon,
|
FlagIcon,
|
||||||
GlobeIcon,
|
GlobeIcon,
|
||||||
@@ -46,6 +47,8 @@ export const StepIcon = ({ type, ...props }: StepIconProps) => {
|
|||||||
return <EditIcon {...props} />
|
return <EditIcon {...props} />
|
||||||
case LogicStepType.CONDITION:
|
case LogicStepType.CONDITION:
|
||||||
return <FilterIcon {...props} />
|
return <FilterIcon {...props} />
|
||||||
|
case LogicStepType.REDIRECT:
|
||||||
|
return <ExternalLinkIcon {...props} />
|
||||||
case IntegrationStepType.GOOGLE_SHEETS:
|
case IntegrationStepType.GOOGLE_SHEETS:
|
||||||
return <GoogleSheetsLogo {...props} />
|
return <GoogleSheetsLogo {...props} />
|
||||||
case IntegrationStepType.GOOGLE_ANALYTICS:
|
case IntegrationStepType.GOOGLE_ANALYTICS:
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ export const StepTypeLabel = ({ type }: Props) => {
|
|||||||
case LogicStepType.CONDITION: {
|
case LogicStepType.CONDITION: {
|
||||||
return <Text>Condition</Text>
|
return <Text>Condition</Text>
|
||||||
}
|
}
|
||||||
|
case LogicStepType.REDIRECT: {
|
||||||
|
return <Text>Redirect</Text>
|
||||||
|
}
|
||||||
case IntegrationStepType.GOOGLE_SHEETS: {
|
case IntegrationStepType.GOOGLE_SHEETS: {
|
||||||
return (
|
return (
|
||||||
<Tooltip label="Google Sheets">
|
<Tooltip label="Google Sheets">
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { ConditionSettingsBody } from './bodies/ConditionSettingsBody'
|
|||||||
import { GoogleAnalyticsSettings } from './bodies/GoogleAnalyticsSettings'
|
import { GoogleAnalyticsSettings } from './bodies/GoogleAnalyticsSettings'
|
||||||
import { GoogleSheetsSettingsBody } from './bodies/GoogleSheetsSettingsBody'
|
import { GoogleSheetsSettingsBody } from './bodies/GoogleSheetsSettingsBody'
|
||||||
import { PhoneNumberSettingsBody } from './bodies/PhoneNumberSettingsBody'
|
import { PhoneNumberSettingsBody } from './bodies/PhoneNumberSettingsBody'
|
||||||
|
import { RedirectSettings } from './bodies/RedirectSettings'
|
||||||
import { SetVariableSettingsBody } from './bodies/SetVariableSettingsBody'
|
import { SetVariableSettingsBody } from './bodies/SetVariableSettingsBody'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -149,6 +150,14 @@ export const StepSettings = ({ step }: { step: Step }) => {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
case LogicStepType.REDIRECT: {
|
||||||
|
return (
|
||||||
|
<RedirectSettings
|
||||||
|
options={step.options}
|
||||||
|
onOptionsChange={handleOptionsChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
case IntegrationStepType.GOOGLE_SHEETS: {
|
case IntegrationStepType.GOOGLE_SHEETS: {
|
||||||
return (
|
return (
|
||||||
<GoogleSheetsSettingsBody
|
<GoogleSheetsSettingsBody
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { FormLabel, Stack } from '@chakra-ui/react'
|
||||||
|
import { DebouncedInput } from 'components/shared/DebouncedInput'
|
||||||
|
import { SwitchWithLabel } from 'components/shared/SwitchWithLabel'
|
||||||
|
import { RedirectOptions } from 'models'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
options?: RedirectOptions
|
||||||
|
onOptionsChange: (options: RedirectOptions) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RedirectSettings = ({ options, onOptionsChange }: Props) => {
|
||||||
|
const handleUrlChange = (url?: string) => onOptionsChange({ ...options, url })
|
||||||
|
|
||||||
|
const handleIsNewTabChange = (isNewTab?: boolean) =>
|
||||||
|
onOptionsChange({ ...options, isNewTab })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Stack>
|
||||||
|
<FormLabel mb="0" htmlFor="tracking-id">
|
||||||
|
Url:
|
||||||
|
</FormLabel>
|
||||||
|
<DebouncedInput
|
||||||
|
id="tracking-id"
|
||||||
|
initialValue={options?.url ?? ''}
|
||||||
|
placeholder="Type a URL..."
|
||||||
|
delay={100}
|
||||||
|
onChange={handleUrlChange}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<SwitchWithLabel
|
||||||
|
id="new-tab"
|
||||||
|
label="Open in new tab?"
|
||||||
|
initialValue={options?.isNewTab ?? false}
|
||||||
|
onCheckChange={handleIsNewTabChange}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -85,6 +85,10 @@ export const StepNodeContent = ({ step }: Props) => {
|
|||||||
case LogicStepType.CONDITION: {
|
case LogicStepType.CONDITION: {
|
||||||
return <ConditionNodeContent step={step} />
|
return <ConditionNodeContent step={step} />
|
||||||
}
|
}
|
||||||
|
case LogicStepType.REDIRECT: {
|
||||||
|
if (!step.options) return <Text color={'gray.500'}>Configure...</Text>
|
||||||
|
return <Text isTruncated>Redirect to {step.options?.url}</Text>
|
||||||
|
}
|
||||||
case IntegrationStepType.GOOGLE_SHEETS: {
|
case IntegrationStepType.GOOGLE_SHEETS: {
|
||||||
if (!step.options) return <Text color={'gray.500'}>Configure...</Text>
|
if (!step.options) return <Text color={'gray.500'}>Configure...</Text>
|
||||||
return <Text>{step.options?.action}</Text>
|
return <Text>{step.options?.action}</Text>
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ import React from 'react'
|
|||||||
export const SaveButton = () => {
|
export const SaveButton = () => {
|
||||||
const { save, isSavingLoading, hasUnsavedChanges } = useTypebot()
|
const { save, isSavingLoading, hasUnsavedChanges } = useTypebot()
|
||||||
|
|
||||||
const onSaveClick = () => {
|
|
||||||
save()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hasUnsavedChanges && (
|
{hasUnsavedChanges && (
|
||||||
@@ -20,7 +16,7 @@ export const SaveButton = () => {
|
|||||||
<Tooltip label="Save changes">
|
<Tooltip label="Save changes">
|
||||||
<IconButton
|
<IconButton
|
||||||
isDisabled={!hasUnsavedChanges}
|
isDisabled={!hasUnsavedChanges}
|
||||||
onClick={onSaveClick}
|
onClick={save}
|
||||||
isLoading={isSavingLoading}
|
isLoading={isSavingLoading}
|
||||||
icon={
|
icon={
|
||||||
hasUnsavedChanges ? <SaveIcon /> : <CheckIcon color="green.400" />
|
hasUnsavedChanges ? <SaveIcon /> : <CheckIcon color="green.400" />
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const headerHeight = 56
|
|||||||
|
|
||||||
export const TypebotHeader = () => {
|
export const TypebotHeader = () => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { typebot, updateTypebot } = useTypebot()
|
const { typebot, updateTypebot, save } = useTypebot()
|
||||||
const { setRightPanel } = useEditor()
|
const { setRightPanel } = useEditor()
|
||||||
|
|
||||||
const handleBackClick = () => {
|
const handleBackClick = () => {
|
||||||
@@ -24,6 +24,12 @@ export const TypebotHeader = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleNameSubmit = (name: string) => updateTypebot({ name })
|
const handleNameSubmit = (name: string) => updateTypebot({ name })
|
||||||
|
|
||||||
|
const handlePreviewClick = async () => {
|
||||||
|
await save()
|
||||||
|
setRightPanel(RightPanel.PREVIEW)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
w="full"
|
w="full"
|
||||||
@@ -95,14 +101,7 @@ export const TypebotHeader = () => {
|
|||||||
|
|
||||||
<HStack right="40px" pos="absolute">
|
<HStack right="40px" pos="absolute">
|
||||||
<SaveButton />
|
<SaveButton />
|
||||||
<Button
|
<Button onClick={handlePreviewClick}>Preview</Button>
|
||||||
onClick={() => {
|
|
||||||
setRightPanel(RightPanel.PREVIEW)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Preview
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<PublishButton />
|
<PublishButton />
|
||||||
</HStack>
|
</HStack>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
107
apps/builder/cypress/fixtures/typebots/logic/redirect.json
Normal file
107
apps/builder/cypress/fixtures/typebots/logic/redirect.json
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
{
|
||||||
|
"id": "ckymkfh1e00562z1a3fjoua3e",
|
||||||
|
"createdAt": "2022-01-20T06:00:51.458Z",
|
||||||
|
"updatedAt": "2022-01-20T06:00:51.458Z",
|
||||||
|
"name": "My typebot",
|
||||||
|
"ownerId": "ckymkff1100362z1a85juyoa8",
|
||||||
|
"publishedTypebotId": null,
|
||||||
|
"folderId": null,
|
||||||
|
"blocks": {
|
||||||
|
"byId": {
|
||||||
|
"bsVJfEW7EZrUnAi9s5ev17": {
|
||||||
|
"id": "bsVJfEW7EZrUnAi9s5ev17",
|
||||||
|
"title": "Start",
|
||||||
|
"stepIds": ["9Ck2yveNjZNHhjyc4HCJAL"],
|
||||||
|
"graphCoordinates": { "x": 0, "y": 0 }
|
||||||
|
},
|
||||||
|
"bmdnpyvzopZ8YVfqsJY7Q8K": {
|
||||||
|
"id": "bmdnpyvzopZ8YVfqsJY7Q8K",
|
||||||
|
"title": "Block #2",
|
||||||
|
"graphCoordinates": { "x": 68, "y": 229 },
|
||||||
|
"stepIds": ["sas16Qqf4TmZEXSexmYpmSd"]
|
||||||
|
},
|
||||||
|
"bnsxmer7DD2R9DogoXTsvHJ": {
|
||||||
|
"id": "bnsxmer7DD2R9DogoXTsvHJ",
|
||||||
|
"title": "Block #3",
|
||||||
|
"graphCoordinates": { "x": 491, "y": 239 },
|
||||||
|
"stepIds": ["sqNGop2aYkXRvJqb9nGtFbD"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"allIds": [
|
||||||
|
"bsVJfEW7EZrUnAi9s5ev17",
|
||||||
|
"bmdnpyvzopZ8YVfqsJY7Q8K",
|
||||||
|
"bnsxmer7DD2R9DogoXTsvHJ"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"byId": {
|
||||||
|
"9Ck2yveNjZNHhjyc4HCJAL": {
|
||||||
|
"id": "9Ck2yveNjZNHhjyc4HCJAL",
|
||||||
|
"type": "start",
|
||||||
|
"label": "Start",
|
||||||
|
"blockId": "bsVJfEW7EZrUnAi9s5ev17",
|
||||||
|
"edgeId": "totLsWG6AQfcFT39CsZwDy"
|
||||||
|
},
|
||||||
|
"sas16Qqf4TmZEXSexmYpmSd": {
|
||||||
|
"id": "sas16Qqf4TmZEXSexmYpmSd",
|
||||||
|
"blockId": "bmdnpyvzopZ8YVfqsJY7Q8K",
|
||||||
|
"type": "choice input",
|
||||||
|
"options": { "itemIds": ["mAgynXh3zmkmWzNyPGVAcf"] }
|
||||||
|
},
|
||||||
|
"sqNGop2aYkXRvJqb9nGtFbD": {
|
||||||
|
"id": "sqNGop2aYkXRvJqb9nGtFbD",
|
||||||
|
"blockId": "bnsxmer7DD2R9DogoXTsvHJ",
|
||||||
|
"type": "Redirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"allIds": [
|
||||||
|
"9Ck2yveNjZNHhjyc4HCJAL",
|
||||||
|
"sas16Qqf4TmZEXSexmYpmSd",
|
||||||
|
"sqNGop2aYkXRvJqb9nGtFbD"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"choiceItems": {
|
||||||
|
"byId": {
|
||||||
|
"mAgynXh3zmkmWzNyPGVAcf": {
|
||||||
|
"id": "mAgynXh3zmkmWzNyPGVAcf",
|
||||||
|
"stepId": "sas16Qqf4TmZEXSexmYpmSd",
|
||||||
|
"content": "Go to URL",
|
||||||
|
"edgeId": "7KgqWB88ufzhDwzvwHuEbN"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"allIds": ["mAgynXh3zmkmWzNyPGVAcf"]
|
||||||
|
},
|
||||||
|
"variables": { "byId": {}, "allIds": [] },
|
||||||
|
"edges": {
|
||||||
|
"byId": {
|
||||||
|
"totLsWG6AQfcFT39CsZwDy": {
|
||||||
|
"from": {
|
||||||
|
"blockId": "bsVJfEW7EZrUnAi9s5ev17",
|
||||||
|
"stepId": "9Ck2yveNjZNHhjyc4HCJAL"
|
||||||
|
},
|
||||||
|
"to": { "blockId": "bmdnpyvzopZ8YVfqsJY7Q8K" },
|
||||||
|
"id": "totLsWG6AQfcFT39CsZwDy"
|
||||||
|
},
|
||||||
|
"7KgqWB88ufzhDwzvwHuEbN": {
|
||||||
|
"from": {
|
||||||
|
"blockId": "bmdnpyvzopZ8YVfqsJY7Q8K",
|
||||||
|
"stepId": "sas16Qqf4TmZEXSexmYpmSd",
|
||||||
|
"nodeId": "mAgynXh3zmkmWzNyPGVAcf"
|
||||||
|
},
|
||||||
|
"to": { "blockId": "bnsxmer7DD2R9DogoXTsvHJ" },
|
||||||
|
"id": "7KgqWB88ufzhDwzvwHuEbN"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"allIds": ["totLsWG6AQfcFT39CsZwDy", "7KgqWB88ufzhDwzvwHuEbN"]
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"general": {
|
||||||
|
"font": "Open Sans",
|
||||||
|
"background": { "type": "None", "content": "#ffffff" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
|
||||||
|
},
|
||||||
|
"publicId": null
|
||||||
|
}
|
||||||
BIN
apps/builder/cypress/fixtures/typebots/logic/redirect.png
Normal file
BIN
apps/builder/cypress/fixtures/typebots/logic/redirect.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
45
apps/builder/cypress/tests/logic/redirect.ts
Normal file
45
apps/builder/cypress/tests/logic/redirect.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { preventUserFromRefreshing } from 'cypress/plugins/utils'
|
||||||
|
import { getIframeBody } from 'cypress/support'
|
||||||
|
|
||||||
|
describe('Redirect', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.task('seed')
|
||||||
|
cy.signOut()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cy.window().then((win) => {
|
||||||
|
win.removeEventListener('beforeunload', preventUserFromRefreshing)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should redirect to URL correctly', () => {
|
||||||
|
cy.loadTypebotFixtureInDatabase('typebots/logic/redirect.json')
|
||||||
|
cy.signIn('test2@gmail.com')
|
||||||
|
cy.visit('/typebots/typebot4/edit')
|
||||||
|
cy.findByText('Configure...').click()
|
||||||
|
cy.findByPlaceholderText('Type a URL...').type('google.com')
|
||||||
|
|
||||||
|
cy.findByRole('button', { name: 'Preview' }).click()
|
||||||
|
getIframeBody().findByRole('button', { name: 'Go to URL' }).click()
|
||||||
|
cy.url().should('eq', 'https://www.google.com/')
|
||||||
|
|
||||||
|
cy.go('back')
|
||||||
|
|
||||||
|
cy.window().then((win) => {
|
||||||
|
cy.stub(win, 'open').as('open')
|
||||||
|
})
|
||||||
|
cy.findByText('Redirect to google.com').click()
|
||||||
|
cy.findByRole('checkbox', { name: 'Open in new tab?' }).check({
|
||||||
|
force: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.findByRole('button', { name: 'Preview' }).click()
|
||||||
|
getIframeBody().findByRole('button', { name: 'Go to URL' }).click()
|
||||||
|
cy.get('@open').should(
|
||||||
|
'have.been.calledOnceWithExactly',
|
||||||
|
'https://google.com',
|
||||||
|
'_blank'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,33 +1,53 @@
|
|||||||
import {
|
import {
|
||||||
LogicStep,
|
LogicStep,
|
||||||
Target,
|
|
||||||
LogicStepType,
|
LogicStepType,
|
||||||
LogicalOperator,
|
LogicalOperator,
|
||||||
ConditionStep,
|
ConditionStep,
|
||||||
Table,
|
Table,
|
||||||
Variable,
|
Variable,
|
||||||
ComparisonOperators,
|
ComparisonOperators,
|
||||||
|
SetVariableStep,
|
||||||
|
RedirectStep,
|
||||||
} from 'models'
|
} from 'models'
|
||||||
import { isDefined } from 'utils'
|
import { isDefined } from 'utils'
|
||||||
|
import { sanitizeUrl } from './utils'
|
||||||
import { isMathFormula, evaluateExpression, parseVariables } from './variable'
|
import { isMathFormula, evaluateExpression, parseVariables } from './variable'
|
||||||
|
|
||||||
|
type EdgeId = string
|
||||||
export const executeLogic = (
|
export const executeLogic = (
|
||||||
step: LogicStep,
|
step: LogicStep,
|
||||||
variables: Table<Variable>,
|
variables: Table<Variable>,
|
||||||
updateVariableValue: (variableId: string, expression: string) => void
|
updateVariableValue: (variableId: string, expression: string) => void
|
||||||
): string | undefined => {
|
): EdgeId | undefined => {
|
||||||
switch (step.type) {
|
switch (step.type) {
|
||||||
case LogicStepType.SET_VARIABLE: {
|
case LogicStepType.SET_VARIABLE:
|
||||||
|
return executeSetVariable(step, variables, updateVariableValue)
|
||||||
|
case LogicStepType.CONDITION:
|
||||||
|
return executeCondition(step, variables)
|
||||||
|
case LogicStepType.REDIRECT:
|
||||||
|
return executeRedirect(step, variables)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const executeSetVariable = (
|
||||||
|
step: SetVariableStep,
|
||||||
|
variables: Table<Variable>,
|
||||||
|
updateVariableValue: (variableId: string, expression: string) => void
|
||||||
|
): EdgeId | undefined => {
|
||||||
if (!step.options?.variableId || !step.options.expressionToEvaluate)
|
if (!step.options?.variableId || !step.options.expressionToEvaluate)
|
||||||
return
|
return step.edgeId
|
||||||
const expression = step.options.expressionToEvaluate
|
const expression = step.options.expressionToEvaluate
|
||||||
const evaluatedExpression = isMathFormula(expression)
|
const evaluatedExpression = isMathFormula(expression)
|
||||||
? evaluateExpression(parseVariables({ text: expression, variables }))
|
? evaluateExpression(parseVariables({ text: expression, variables }))
|
||||||
: expression
|
: expression
|
||||||
updateVariableValue(step.options.variableId, evaluatedExpression)
|
updateVariableValue(step.options.variableId, evaluatedExpression)
|
||||||
return
|
return step.edgeId
|
||||||
}
|
}
|
||||||
case LogicStepType.CONDITION: {
|
|
||||||
|
const executeCondition = (
|
||||||
|
step: ConditionStep,
|
||||||
|
variables: Table<Variable>
|
||||||
|
): EdgeId | undefined => {
|
||||||
const isConditionPassed =
|
const isConditionPassed =
|
||||||
step.options?.logicalOperator === LogicalOperator.AND
|
step.options?.logicalOperator === LogicalOperator.AND
|
||||||
? step.options?.comparisons.allIds.every(
|
? step.options?.comparisons.allIds.every(
|
||||||
@@ -38,8 +58,6 @@ export const executeLogic = (
|
|||||||
)
|
)
|
||||||
return isConditionPassed ? step.trueEdgeId : step.falseEdgeId
|
return isConditionPassed ? step.trueEdgeId : step.falseEdgeId
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const executeComparison =
|
const executeComparison =
|
||||||
(step: ConditionStep, variables: Table<Variable>) =>
|
(step: ConditionStep, variables: Table<Variable>) =>
|
||||||
@@ -70,3 +88,15 @@ const executeComparison =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const executeRedirect = (
|
||||||
|
step: RedirectStep,
|
||||||
|
variables: Table<Variable>
|
||||||
|
): EdgeId | undefined => {
|
||||||
|
if (!step.options?.url) return step.edgeId
|
||||||
|
window.open(
|
||||||
|
sanitizeUrl(parseVariables({ text: step.options?.url, variables })),
|
||||||
|
step.options.isNewTab ? '_blank' : '_self'
|
||||||
|
)
|
||||||
|
return step.edgeId
|
||||||
|
}
|
||||||
|
|||||||
7
packages/bot-engine/src/services/utils.ts
Normal file
7
packages/bot-engine/src/services/utils.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export const sanitizeUrl = (url: string): string =>
|
||||||
|
url.startsWith('http') ||
|
||||||
|
url.startsWith('mailto:') ||
|
||||||
|
url.startsWith('tel:') ||
|
||||||
|
url.startsWith('sms:')
|
||||||
|
? url
|
||||||
|
: `https://${url}`
|
||||||
@@ -1,20 +1,36 @@
|
|||||||
import { StepBase } from '.'
|
import { StepBase } from '.'
|
||||||
import { Table } from '../..'
|
import { Table } from '../..'
|
||||||
|
|
||||||
export type LogicStep = SetVariableStep | ConditionStep
|
export type LogicStep = SetVariableStep | ConditionStep | RedirectStep
|
||||||
|
|
||||||
export enum LogicStepType {
|
export enum LogicStepType {
|
||||||
SET_VARIABLE = 'Set variable',
|
SET_VARIABLE = 'Set variable',
|
||||||
CONDITION = 'Condition',
|
CONDITION = 'Condition',
|
||||||
|
REDIRECT = 'Redirect',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LogicStepOptions = SetVariableOptions | ConditionOptions
|
export type LogicStepOptions =
|
||||||
|
| SetVariableOptions
|
||||||
|
| ConditionOptions
|
||||||
|
| RedirectOptions
|
||||||
|
|
||||||
export type SetVariableStep = StepBase & {
|
export type SetVariableStep = StepBase & {
|
||||||
type: LogicStepType.SET_VARIABLE
|
type: LogicStepType.SET_VARIABLE
|
||||||
options?: SetVariableOptions
|
options?: SetVariableOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ConditionStep = StepBase & {
|
||||||
|
type: LogicStepType.CONDITION
|
||||||
|
options: ConditionOptions
|
||||||
|
trueEdgeId?: string
|
||||||
|
falseEdgeId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RedirectStep = StepBase & {
|
||||||
|
type: LogicStepType.REDIRECT
|
||||||
|
options?: RedirectOptions
|
||||||
|
}
|
||||||
|
|
||||||
export enum LogicalOperator {
|
export enum LogicalOperator {
|
||||||
OR = 'OR',
|
OR = 'OR',
|
||||||
AND = 'AND',
|
AND = 'AND',
|
||||||
@@ -29,13 +45,6 @@ export enum ComparisonOperators {
|
|||||||
IS_SET = 'Is set',
|
IS_SET = 'Is set',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ConditionStep = StepBase & {
|
|
||||||
type: LogicStepType.CONDITION
|
|
||||||
options: ConditionOptions
|
|
||||||
trueEdgeId?: string
|
|
||||||
falseEdgeId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ConditionOptions = {
|
export type ConditionOptions = {
|
||||||
comparisons: Table<Comparison>
|
comparisons: Table<Comparison>
|
||||||
logicalOperator?: LogicalOperator
|
logicalOperator?: LogicalOperator
|
||||||
@@ -52,3 +61,8 @@ export type SetVariableOptions = {
|
|||||||
variableId?: string
|
variableId?: string
|
||||||
expressionToEvaluate?: string
|
expressionToEvaluate?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RedirectOptions = {
|
||||||
|
url?: string
|
||||||
|
isNewTab?: boolean
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user