import {
VStack,
Tag,
Text,
Tooltip,
useColorModeValue,
theme,
} from '@chakra-ui/react'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
import React, { useMemo } from 'react'
import { useEndpoints } from '../../providers/EndpointsProvider'
import { useGroupsCoordinates } from '../../providers/GroupsCoordinateProvider'
import { hasProPerks } from '@/features/billing/helpers/hasProPerks'
import { computeDropOffPath } from '../../helpers/computeDropOffPath'
import { computeSourceCoordinates } from '../../helpers/computeSourceCoordinates'
import {
TotalAnswers,
TotalVisitedEdges,
} from '@typebot.io/schemas/features/analytics'
import { computeTotalUsersAtBlock } from '@/features/analytics/helpers/computeTotalUsersAtBlock'
import { blockHasItems, byId } from '@typebot.io/lib'
import { groupWidth } from '../../constants'
import { getTotalAnswersAtBlock } from '@/features/analytics/helpers/getTotalAnswersAtBlock'
export const dropOffBoxDimensions = {
width: 100,
height: 55,
}
export const dropOffSegmentLength = 80
const dropOffSegmentMinWidth = 2
const dropOffSegmentMaxWidth = 20
export const dropOffStubLength = 30
type Props = {
blockId: string
totalVisitedEdges: TotalVisitedEdges[]
totalAnswers: TotalAnswers[]
onUnlockProPlanClick?: () => void
}
export const DropOffEdge = ({
totalVisitedEdges,
totalAnswers,
blockId,
onUnlockProPlanClick,
}: Props) => {
const dropOffColor = useColorModeValue(
theme.colors.red[500],
theme.colors.red[400]
)
const { workspace } = useWorkspace()
const { groupsCoordinates } = useGroupsCoordinates()
const { sourceEndpointYOffsets: sourceEndpoints } = useEndpoints()
const { publishedTypebot } = useTypebot()
const currentBlockId = useMemo(
() =>
publishedTypebot?.groups.flatMap((g) => g.blocks)?.find(byId(blockId))
?.id,
[blockId, publishedTypebot?.groups]
)
const isWorkspaceProPlan = hasProPerks(workspace)
const { totalDroppedUser, dropOffRate } = useMemo(() => {
if (!publishedTypebot || !currentBlockId) return {}
const totalUsersAtBlock = computeTotalUsersAtBlock(currentBlockId, {
publishedTypebot,
totalVisitedEdges,
totalAnswers,
})
const totalBlockReplies = getTotalAnswersAtBlock(currentBlockId, {
publishedTypebot,
totalAnswers,
})
if (totalUsersAtBlock === 0) return {}
const totalDroppedUser = totalUsersAtBlock - totalBlockReplies
return {
totalDroppedUser,
dropOffRate: Math.round((totalDroppedUser / totalUsersAtBlock) * 100),
}
}, [currentBlockId, publishedTypebot, totalAnswers, totalVisitedEdges])
const sourceTop = useMemo(() => {
const blockTop = currentBlockId
? sourceEndpoints.get(currentBlockId)?.y
: undefined
if (blockTop) return blockTop
const block = publishedTypebot?.groups
.flatMap((group) => group.blocks)
.find((block) => block.id === currentBlockId)
if (!block || !blockHasItems(block)) return 0
const itemId = block.items.at(-1)?.id
if (!itemId) return 0
return sourceEndpoints.get(itemId)?.y
}, [currentBlockId, publishedTypebot?.groups, sourceEndpoints])
const endpointCoordinates = useMemo(() => {
const groupId = publishedTypebot?.groups.find((group) =>
group.blocks.some((block) => block.id === currentBlockId)
)?.id
if (!groupId) return undefined
const coordinates = groupsCoordinates[groupId]
if (!coordinates) return undefined
return computeSourceCoordinates({
sourcePosition: coordinates,
sourceTop: sourceTop ?? 0,
elementWidth: groupWidth,
})
}, [publishedTypebot?.groups, groupsCoordinates, sourceTop, currentBlockId])
const isLastBlock = useMemo(() => {
if (!publishedTypebot) return false
const lastBlock = publishedTypebot.groups
.find((group) =>
group.blocks.some((block) => block.id === currentBlockId)
)
?.blocks.at(-1)
return lastBlock?.id === currentBlockId
}, [publishedTypebot, currentBlockId])
if (!endpointCoordinates) return null
return (
<>
1 ? 's' : ''
} left. This represents ${dropOffRate}% of the users who saw this input.`
: 'Upgrade your plan to PRO to reveal drop-off rate.'
}
placement="top"
>
{isWorkspaceProPlan ? (
dropOffRate
) : (
X
)}
%
{isWorkspaceProPlan ? (
totalDroppedUser
) : (
NN
)}{' '}
user{(totalDroppedUser ?? 2) > 1 ? 's' : ''}
>
)
}