fix(analytics): 🐛 Analytics board
This commit is contained in:
100
apps/builder/components/shared/Graph/Edges/DropOffEdge.tsx
Normal file
100
apps/builder/components/shared/Graph/Edges/DropOffEdge.tsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { VStack, Tag, Text } from '@chakra-ui/react'
|
||||||
|
import { useGraph } from 'contexts/GraphContext'
|
||||||
|
import { useTypebot } from 'contexts/TypebotContext'
|
||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import { AnswersCount } from 'services/analytics'
|
||||||
|
import {
|
||||||
|
getEndpointTopOffset,
|
||||||
|
computeSourceCoordinates,
|
||||||
|
computeDropOffPath,
|
||||||
|
} from 'services/graph'
|
||||||
|
import { byId, isDefined } from 'utils'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
blockId: string
|
||||||
|
answersCounts: AnswersCount[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DropOffEdge = ({ answersCounts, blockId }: Props) => {
|
||||||
|
const { sourceEndpoints, graphPosition, blocksCoordinates } = useGraph()
|
||||||
|
const { publishedTypebot } = useTypebot()
|
||||||
|
|
||||||
|
const totalAnswers = useMemo(
|
||||||
|
() => answersCounts.find((a) => a.blockId === blockId)?.totalAnswers,
|
||||||
|
[answersCounts, blockId]
|
||||||
|
)
|
||||||
|
|
||||||
|
const { totalDroppedUser, dropOffRate } = useMemo(() => {
|
||||||
|
if (!publishedTypebot || totalAnswers === undefined)
|
||||||
|
return { previousTotal: undefined, dropOffRate: undefined }
|
||||||
|
const previousBlockIds = publishedTypebot.edges
|
||||||
|
.map((edge) =>
|
||||||
|
edge.to.blockId === blockId ? edge.from.blockId : undefined
|
||||||
|
)
|
||||||
|
.filter(isDefined)
|
||||||
|
const previousTotal = answersCounts
|
||||||
|
.filter((a) => previousBlockIds.includes(a.blockId))
|
||||||
|
.reduce((prev, acc) => acc.totalAnswers + prev, 0)
|
||||||
|
if (previousTotal === 0)
|
||||||
|
return { previousTotal: undefined, dropOffRate: undefined }
|
||||||
|
const totalDroppedUser = previousTotal - totalAnswers
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalDroppedUser,
|
||||||
|
dropOffRate: Math.round((totalDroppedUser / previousTotal) * 100),
|
||||||
|
}
|
||||||
|
}, [answersCounts, blockId, totalAnswers, publishedTypebot])
|
||||||
|
|
||||||
|
const block = publishedTypebot?.blocks.find(byId(blockId))
|
||||||
|
const sourceTop = useMemo(
|
||||||
|
() =>
|
||||||
|
getEndpointTopOffset(
|
||||||
|
graphPosition,
|
||||||
|
sourceEndpoints,
|
||||||
|
block?.steps[block.steps.length - 1].id,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[block?.steps, graphPosition, sourceEndpoints, blocksCoordinates]
|
||||||
|
)
|
||||||
|
|
||||||
|
const labelCoordinates = useMemo(() => {
|
||||||
|
if (!blocksCoordinates[blockId]) return
|
||||||
|
return computeSourceCoordinates(blocksCoordinates[blockId], sourceTop ?? 0)
|
||||||
|
}, [blocksCoordinates, blockId, sourceTop])
|
||||||
|
|
||||||
|
if (!labelCoordinates) return <></>
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<path
|
||||||
|
d={computeDropOffPath(
|
||||||
|
{ x: labelCoordinates.x - 300, y: labelCoordinates.y },
|
||||||
|
sourceTop ?? 0
|
||||||
|
)}
|
||||||
|
stroke="#e53e3e"
|
||||||
|
strokeWidth="2px"
|
||||||
|
markerEnd="url(#red-arrow)"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
<foreignObject
|
||||||
|
width="80"
|
||||||
|
height="80"
|
||||||
|
x={labelCoordinates.x - 20}
|
||||||
|
y={labelCoordinates.y + 80}
|
||||||
|
>
|
||||||
|
<VStack
|
||||||
|
bgColor={'red.500'}
|
||||||
|
color="white"
|
||||||
|
rounded="md"
|
||||||
|
p="2"
|
||||||
|
justifyContent="center"
|
||||||
|
w="full"
|
||||||
|
h="full"
|
||||||
|
>
|
||||||
|
<Text>{dropOffRate}%</Text>
|
||||||
|
<Tag colorScheme="red">{totalDroppedUser} users</Tag>
|
||||||
|
</VStack>
|
||||||
|
</foreignObject>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ export const Edge = ({ edge }: { edge: EdgeProps }) => {
|
|||||||
targetEndpoints,
|
targetEndpoints,
|
||||||
graphPosition,
|
graphPosition,
|
||||||
blocksCoordinates,
|
blocksCoordinates,
|
||||||
|
isReadOnly,
|
||||||
} = useGraph()
|
} = useGraph()
|
||||||
const isPreviewing = previewingEdge?.id === edge.id
|
const isPreviewing = previewingEdge?.id === edge.id
|
||||||
|
|
||||||
@@ -35,14 +36,21 @@ export const Edge = ({ edge }: { edge: EdgeProps }) => {
|
|||||||
getEndpointTopOffset(
|
getEndpointTopOffset(
|
||||||
graphPosition,
|
graphPosition,
|
||||||
sourceEndpoints,
|
sourceEndpoints,
|
||||||
getSourceEndpointId(edge)
|
getSourceEndpointId(edge),
|
||||||
|
isReadOnly
|
||||||
),
|
),
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[edge, graphPosition, sourceEndpoints, sourceBlockCoordinates?.y]
|
[edge, graphPosition, sourceEndpoints, sourceBlockCoordinates?.y]
|
||||||
)
|
)
|
||||||
const targetTop = useMemo(
|
const targetTop = useMemo(
|
||||||
() => getEndpointTopOffset(graphPosition, targetEndpoints, edge?.to.stepId),
|
() =>
|
||||||
|
getEndpointTopOffset(
|
||||||
|
graphPosition,
|
||||||
|
targetEndpoints,
|
||||||
|
edge?.to.stepId,
|
||||||
|
isReadOnly
|
||||||
|
),
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[graphPosition, targetEndpoints, edge?.to.stepId, targetBlockCoordinates?.y]
|
[graphPosition, targetEndpoints, edge?.to.stepId, targetBlockCoordinates?.y]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import { chakra } from '@chakra-ui/system'
|
import { chakra } from '@chakra-ui/system'
|
||||||
import { Edge as EdgeProps } from 'models'
|
import { Edge as EdgeProps } from 'models'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { AnswersCount } from 'services/analytics'
|
||||||
import { DrawingEdge } from './DrawingEdge'
|
import { DrawingEdge } from './DrawingEdge'
|
||||||
|
import { DropOffEdge } from './DropOffEdge'
|
||||||
import { Edge } from './Edge'
|
import { Edge } from './Edge'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
edges: EdgeProps[]
|
edges: EdgeProps[]
|
||||||
|
answersCounts?: AnswersCount[]
|
||||||
}
|
}
|
||||||
export const Edges = ({ edges }: Props) => {
|
export const Edges = ({ edges, answersCounts }: Props) => {
|
||||||
return (
|
return (
|
||||||
<chakra.svg
|
<chakra.svg
|
||||||
width="full"
|
width="full"
|
||||||
@@ -21,6 +24,13 @@ export const Edges = ({ edges }: Props) => {
|
|||||||
{edges.map((edge) => (
|
{edges.map((edge) => (
|
||||||
<Edge key={edge.id} edge={edge} />
|
<Edge key={edge.id} edge={edge} />
|
||||||
))}
|
))}
|
||||||
|
{answersCounts?.slice(1)?.map((answerCount) => (
|
||||||
|
<DropOffEdge
|
||||||
|
key={answerCount.blockId}
|
||||||
|
answersCounts={answersCounts}
|
||||||
|
blockId={answerCount.blockId}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
<marker
|
<marker
|
||||||
id={'arrow'}
|
id={'arrow'}
|
||||||
refX="8"
|
refX="8"
|
||||||
@@ -51,6 +61,21 @@ export const Edges = ({ edges }: Props) => {
|
|||||||
fill="#1a5fff"
|
fill="#1a5fff"
|
||||||
/>
|
/>
|
||||||
</marker>
|
</marker>
|
||||||
|
<marker
|
||||||
|
id={'red-arrow'}
|
||||||
|
refX="8"
|
||||||
|
refY="4"
|
||||||
|
orient="auto"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
markerUnits="userSpaceOnUse"
|
||||||
|
markerWidth="20"
|
||||||
|
markerHeight="20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M7.07138888,5.50174526 L2.43017246,7.82235347 C1.60067988,8.23709976 0.592024983,7.90088146 0.177278692,7.07138888 C0.0606951226,6.83822174 0,6.58111307 0,6.32042429 L0,1.67920787 C0,0.751806973 0.751806973,0 1.67920787,0 C1.93989666,0 2.19700532,0.0606951226 2.43017246,0.177278692 L7,3 C7.82949258,3.41474629 8.23709976,3.92128809 7.82235347,4.75078067 C7.6598671,5.07575341 7.39636161,5.33925889 7.07138888,5.50174526 Z"
|
||||||
|
fill="#e53e3e"
|
||||||
|
/>
|
||||||
|
</marker>
|
||||||
</chakra.svg>
|
</chakra.svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,20 +6,23 @@ import { useStepDnd } from 'contexts/GraphDndContext'
|
|||||||
import { Edges } from './Edges'
|
import { Edges } from './Edges'
|
||||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||||
import { headerHeight } from 'components/shared/TypebotHeader/TypebotHeader'
|
import { headerHeight } from 'components/shared/TypebotHeader/TypebotHeader'
|
||||||
import { DraggableStepType } from 'models'
|
import { Block, DraggableStepType, PublicTypebot, Typebot } from 'models'
|
||||||
import { generate } from 'short-uuid'
|
import { generate } from 'short-uuid'
|
||||||
import { AnswersCount } from 'services/analytics'
|
import { AnswersCount } from 'services/analytics'
|
||||||
import { DropOffNode } from './Nodes/DropOffNode'
|
|
||||||
|
|
||||||
export const Graph = ({
|
export const Graph = ({
|
||||||
|
typebot,
|
||||||
answersCounts,
|
answersCounts,
|
||||||
...props
|
...props
|
||||||
}: { answersCounts?: AnswersCount[] } & FlexProps) => {
|
}: {
|
||||||
|
typebot?: Typebot | PublicTypebot
|
||||||
|
answersCounts?: AnswersCount[]
|
||||||
|
} & FlexProps) => {
|
||||||
const { draggedStepType, setDraggedStepType, draggedStep, setDraggedStep } =
|
const { draggedStepType, setDraggedStepType, draggedStep, setDraggedStep } =
|
||||||
useStepDnd()
|
useStepDnd()
|
||||||
const graphContainerRef = useRef<HTMLDivElement | null>(null)
|
const graphContainerRef = useRef<HTMLDivElement | null>(null)
|
||||||
const editorContainerRef = useRef<HTMLDivElement | null>(null)
|
const editorContainerRef = useRef<HTMLDivElement | null>(null)
|
||||||
const { createBlock, typebot } = useTypebot()
|
const { createBlock } = useTypebot()
|
||||||
const {
|
const {
|
||||||
graphPosition,
|
graphPosition,
|
||||||
setGraphPosition,
|
setGraphPosition,
|
||||||
@@ -96,16 +99,9 @@ export const Graph = ({
|
|||||||
transform,
|
transform,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Edges edges={typebot?.edges ?? []} />
|
<Edges edges={typebot?.edges ?? []} answersCounts={answersCounts} />
|
||||||
{typebot?.blocks.map((block, idx) => (
|
{typebot?.blocks.map((block, idx) => (
|
||||||
<BlockNode block={block} blockIndex={idx} key={block.id} />
|
<BlockNode block={block as Block} blockIndex={idx} key={block.id} />
|
||||||
))}
|
|
||||||
{answersCounts?.map((answersCount) => (
|
|
||||||
<DropOffNode
|
|
||||||
key={answersCount.blockId}
|
|
||||||
answersCounts={answersCounts}
|
|
||||||
blockId={answersCount.blockId}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ export const BlockNode = ({ block, blockIndex }: Props) => {
|
|||||||
useEventListener('mousemove', handleMouseMove)
|
useEventListener('mousemove', handleMouseMove)
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
|
if (isReadOnly) return
|
||||||
if (mouseOverBlock?.id !== block.id && !isStartBlock)
|
if (mouseOverBlock?.id !== block.id && !isStartBlock)
|
||||||
setMouseOverBlock({ id: block.id, ref: blockRef })
|
setMouseOverBlock({ id: block.id, ref: blockRef })
|
||||||
if (connectingIds)
|
if (connectingIds)
|
||||||
@@ -94,6 +95,7 @@ export const BlockNode = ({ block, blockIndex }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = () => {
|
||||||
|
if (isReadOnly) return
|
||||||
setMouseOverBlock(undefined)
|
setMouseOverBlock(undefined)
|
||||||
if (connectingIds) setConnectingIds({ ...connectingIds, target: undefined })
|
if (connectingIds) setConnectingIds({ ...connectingIds, target: undefined })
|
||||||
}
|
}
|
||||||
@@ -134,7 +136,7 @@ export const BlockNode = ({ block, blockIndex }: Props) => {
|
|||||||
defaultValue={block.title}
|
defaultValue={block.title}
|
||||||
onSubmit={handleTitleSubmit}
|
onSubmit={handleTitleSubmit}
|
||||||
fontWeight="semibold"
|
fontWeight="semibold"
|
||||||
isDisabled={isReadOnly}
|
pointerEvents={isReadOnly ? 'none' : 'auto'}
|
||||||
>
|
>
|
||||||
<EditablePreview
|
<EditablePreview
|
||||||
_hover={{ bgColor: 'gray.300' }}
|
_hover={{ bgColor: 'gray.300' }}
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import { Tag, Text, VStack } from '@chakra-ui/react'
|
|
||||||
import { useTypebot } from 'contexts/TypebotContext'
|
|
||||||
import React, { useMemo } from 'react'
|
|
||||||
import { AnswersCount } from 'services/analytics'
|
|
||||||
import { computeSourceCoordinates } from 'services/graph'
|
|
||||||
import { byId, isDefined } from 'utils'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
answersCounts: AnswersCount[]
|
|
||||||
blockId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DropOffNode = ({ answersCounts, blockId }: Props) => {
|
|
||||||
const { typebot } = useTypebot()
|
|
||||||
|
|
||||||
const totalAnswers = useMemo(
|
|
||||||
() => answersCounts.find((a) => a.blockId === blockId)?.totalAnswers,
|
|
||||||
[answersCounts, blockId]
|
|
||||||
)
|
|
||||||
|
|
||||||
const { totalDroppedUser, dropOffRate } = useMemo(() => {
|
|
||||||
if (!typebot || totalAnswers === undefined)
|
|
||||||
return { previousTotal: undefined, dropOffRate: undefined }
|
|
||||||
const previousBlockIds = typebot.edges
|
|
||||||
.map((edge) =>
|
|
||||||
edge.to.blockId === blockId ? edge.from.blockId : undefined
|
|
||||||
)
|
|
||||||
.filter((blockId) => isDefined(blockId))
|
|
||||||
const previousTotal = answersCounts
|
|
||||||
.filter((a) => previousBlockIds.includes(a.blockId))
|
|
||||||
.reduce((prev, acc) => acc.totalAnswers + prev, 0)
|
|
||||||
if (previousTotal === 0)
|
|
||||||
return { previousTotal: undefined, dropOffRate: undefined }
|
|
||||||
const totalDroppedUser = previousTotal - totalAnswers
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalDroppedUser,
|
|
||||||
dropOffRate: Math.round((totalDroppedUser / previousTotal) * 100),
|
|
||||||
}
|
|
||||||
}, [answersCounts, blockId, totalAnswers, typebot])
|
|
||||||
|
|
||||||
const labelCoordinates = useMemo(() => {
|
|
||||||
const sourceBlock = typebot?.blocks.find(byId(blockId))
|
|
||||||
if (!sourceBlock) return
|
|
||||||
return computeSourceCoordinates(
|
|
||||||
sourceBlock?.graphCoordinates,
|
|
||||||
sourceBlock?.steps.length - 1
|
|
||||||
)
|
|
||||||
}, [blockId, typebot])
|
|
||||||
|
|
||||||
if (!labelCoordinates) return <></>
|
|
||||||
return (
|
|
||||||
<VStack
|
|
||||||
bgColor={'red.500'}
|
|
||||||
color="white"
|
|
||||||
rounded="md"
|
|
||||||
p="2"
|
|
||||||
justifyContent="center"
|
|
||||||
style={{
|
|
||||||
transform: `translate(${labelCoordinates.x - 20}px, ${
|
|
||||||
labelCoordinates.y + 80
|
|
||||||
}px)`,
|
|
||||||
}}
|
|
||||||
pos="absolute"
|
|
||||||
>
|
|
||||||
<Text>{dropOffRate}%</Text>
|
|
||||||
<Tag colorScheme="red">{totalDroppedUser} users</Tag>
|
|
||||||
</VStack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -115,7 +115,11 @@ export const StepNodesList = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={1} transition="none">
|
<Stack
|
||||||
|
spacing={1}
|
||||||
|
transition="none"
|
||||||
|
pointerEvents={isReadOnly ? 'none' : 'auto'}
|
||||||
|
>
|
||||||
<Flex
|
<Flex
|
||||||
ref={handlePushElementRef(0)}
|
ref={handlePushElementRef(0)}
|
||||||
h={
|
h={
|
||||||
@@ -135,7 +139,7 @@ export const StepNodesList = ({
|
|||||||
key={step.id}
|
key={step.id}
|
||||||
step={step}
|
step={step}
|
||||||
indices={{ blockIndex, stepIndex: idx }}
|
indices={{ blockIndex, stepIndex: idx }}
|
||||||
isConnectable={!isReadOnly && steps.length - 1 === idx}
|
isConnectable={steps.length - 1 === idx}
|
||||||
onMouseDown={handleStepMouseDown(idx)}
|
onMouseDown={handleStepMouseDown(idx)}
|
||||||
/>
|
/>
|
||||||
<Flex
|
<Flex
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Block, Edge, IdMap, Source, Step, Target } from 'models'
|
import { Block, Edge, IdMap, PublicBlock, Source, Step, Target } from 'models'
|
||||||
import {
|
import {
|
||||||
createContext,
|
createContext,
|
||||||
Dispatch,
|
Dispatch,
|
||||||
@@ -86,7 +86,7 @@ export const GraphProvider = ({
|
|||||||
isReadOnly = false,
|
isReadOnly = false,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
blocks: Block[]
|
blocks: (Block | PublicBlock)[]
|
||||||
isReadOnly?: boolean
|
isReadOnly?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
const [graphPosition, setGraphPosition] = useState(graphPositionDefaultValue)
|
const [graphPosition, setGraphPosition] = useState(graphPositionDefaultValue)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const Board = () => {
|
|||||||
<GraphDndContext>
|
<GraphDndContext>
|
||||||
<StepsSideBar />
|
<StepsSideBar />
|
||||||
<GraphProvider blocks={typebot?.blocks ?? []}>
|
<GraphProvider blocks={typebot?.blocks ?? []}>
|
||||||
<Graph flex="1" />
|
{typebot && <Graph flex="1" typebot={typebot} />}
|
||||||
<BoardMenuButton pos="absolute" right="40px" top="20px" />
|
<BoardMenuButton pos="absolute" right="40px" top="20px" />
|
||||||
{rightPanel === RightPanel.PREVIEW && <PreviewDrawer />}
|
{rightPanel === RightPanel.PREVIEW && <PreviewDrawer />}
|
||||||
</GraphProvider>
|
</GraphProvider>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Flex, useToast } from '@chakra-ui/react'
|
import { Flex, useToast } from '@chakra-ui/react'
|
||||||
import { StatsCards } from 'components/analytics/StatsCards'
|
import { StatsCards } from 'components/analytics/StatsCards'
|
||||||
import { Graph } from 'components/shared/Graph'
|
import { Graph } from 'components/shared/Graph'
|
||||||
|
import { GraphProvider } from 'contexts/GraphContext'
|
||||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||||
import { Stats } from 'models'
|
import { Stats } from 'models'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
@@ -25,14 +26,17 @@ export const AnalyticsContent = ({ stats }: { stats?: Stats }) => {
|
|||||||
h="full"
|
h="full"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
>
|
>
|
||||||
{publishedTypebot && (
|
{publishedTypebot && answersCounts && stats && (
|
||||||
<Graph
|
<GraphProvider blocks={publishedTypebot?.blocks ?? []} isReadOnly>
|
||||||
flex="1"
|
<Graph
|
||||||
answersCounts={[
|
flex="1"
|
||||||
{ blockId: 'start-block', totalAnswers: stats?.totalViews ?? 0 },
|
typebot={publishedTypebot}
|
||||||
...(answersCounts ?? []),
|
answersCounts={[
|
||||||
]}
|
{ ...answersCounts[0], totalAnswers: stats?.totalStarts },
|
||||||
/>
|
...answersCounts?.slice(1),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</GraphProvider>
|
||||||
)}
|
)}
|
||||||
<StatsCards stats={stats} pos="absolute" top={10} />
|
<StatsCards stats={stats} pos="absolute" top={10} />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import { headerHeight } from 'components/shared/TypebotHeader'
|
|||||||
|
|
||||||
export const computeDropOffPath = (
|
export const computeDropOffPath = (
|
||||||
sourcePosition: Coordinates,
|
sourcePosition: Coordinates,
|
||||||
sourceStepIndex: number
|
sourceTop: number
|
||||||
) => {
|
) => {
|
||||||
const sourceCoord = computeSourceCoordinates(sourcePosition, sourceStepIndex)
|
const sourceCoord = computeSourceCoordinates(sourcePosition, sourceTop)
|
||||||
const segments = computeTwoSegments(sourceCoord, {
|
const segments = computeTwoSegments(sourceCoord, {
|
||||||
x: sourceCoord.x + 20,
|
x: sourceCoord.x + 20,
|
||||||
y: sourceCoord.y + 80,
|
y: sourceCoord.y + 80,
|
||||||
@@ -276,7 +276,8 @@ export const computeEdgePathToMouse = ({
|
|||||||
export const getEndpointTopOffset = (
|
export const getEndpointTopOffset = (
|
||||||
graphPosition: Coordinates,
|
graphPosition: Coordinates,
|
||||||
endpoints: IdMap<Endpoint>,
|
endpoints: IdMap<Endpoint>,
|
||||||
endpointId?: string
|
endpointId?: string,
|
||||||
|
isAnalytics?: boolean
|
||||||
): number | undefined => {
|
): number | undefined => {
|
||||||
if (!endpointId) return
|
if (!endpointId) return
|
||||||
const endpointRef = endpoints[endpointId]?.ref
|
const endpointRef = endpoints[endpointId]?.ref
|
||||||
@@ -285,7 +286,8 @@ export const getEndpointTopOffset = (
|
|||||||
8 +
|
8 +
|
||||||
(endpointRef.current?.getBoundingClientRect().top ?? 0) -
|
(endpointRef.current?.getBoundingClientRect().top ?? 0) -
|
||||||
graphPosition.y -
|
graphPosition.y -
|
||||||
headerHeight
|
headerHeight -
|
||||||
|
(isAnalytics ? 60 : 0)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import imageCompression from 'browser-image-compression'
|
import imageCompression from 'browser-image-compression'
|
||||||
import { Parser } from 'htmlparser2'
|
import { Parser } from 'htmlparser2'
|
||||||
import { Step, Typebot } from 'models'
|
import { PublicStep, Step, Typebot } from 'models'
|
||||||
|
|
||||||
export const fetcher = async (input: RequestInfo, init?: RequestInit) => {
|
export const fetcher = async (input: RequestInfo, init?: RequestInit) => {
|
||||||
const res = await fetch(input, init)
|
const res = await fetch(input, init)
|
||||||
@@ -98,7 +98,7 @@ export const removeUndefinedFields = <T>(obj: T): T =>
|
|||||||
{} as T
|
{} as T
|
||||||
)
|
)
|
||||||
|
|
||||||
export const stepHasOptions = (step: Step) => 'options' in step
|
export const stepHasOptions = (step: Step | PublicStep) => 'options' in step
|
||||||
|
|
||||||
export const parseVariableHighlight = (content: string, typebot: Typebot) => {
|
export const parseVariableHighlight = (content: string, typebot: Typebot) => {
|
||||||
const varNames = typebot.variables.map((v) => v.name)
|
const varNames = typebot.variables.map((v) => v.name)
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export const isTextBubbleStep = (
|
|||||||
): step is TextBubbleStep => step.type === BubbleStepType.TEXT
|
): step is TextBubbleStep => step.type === BubbleStepType.TEXT
|
||||||
|
|
||||||
export const isMediaBubbleStep = (
|
export const isMediaBubbleStep = (
|
||||||
step: Step
|
step: Step | PublicStep
|
||||||
): step is ImageBubbleStep | VideoBubbleStep =>
|
): step is ImageBubbleStep | VideoBubbleStep =>
|
||||||
step.type === BubbleStepType.IMAGE || step.type === BubbleStepType.VIDEO
|
step.type === BubbleStepType.IMAGE || step.type === BubbleStepType.VIDEO
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ export const stepTypeHasItems = (
|
|||||||
type === LogicStepType.CONDITION || type === InputStepType.CHOICE
|
type === LogicStepType.CONDITION || type === InputStepType.CHOICE
|
||||||
|
|
||||||
export const stepHasItems = (
|
export const stepHasItems = (
|
||||||
step: Step
|
step: Step | PublicStep
|
||||||
): step is ConditionStep | ChoiceInputStep => 'items' in step
|
): step is ConditionStep | ChoiceInputStep => 'items' in step
|
||||||
|
|
||||||
export const byId = (id?: string) => (obj: { id: string }) => obj.id === id
|
export const byId = (id?: string) => (obj: { id: string }) => obj.id === id
|
||||||
|
|||||||
Reference in New Issue
Block a user