♻️ (builder) Change to features-centric folder structure
This commit is contained in:
committed by
Baptiste Arnaud
parent
3686465a85
commit
643571fe7d
32
apps/builder/src/features/analytics/analytics.spec.ts
Normal file
32
apps/builder/src/features/analytics/analytics.spec.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { getTestAsset } from '@/test/utils/playwright'
|
||||
import test, { expect } from '@playwright/test'
|
||||
import cuid from 'cuid'
|
||||
import {
|
||||
importTypebotInDatabase,
|
||||
injectFakeResults,
|
||||
} from 'utils/playwright/databaseActions'
|
||||
import { starterWorkspaceId } from 'utils/playwright/databaseSetup'
|
||||
|
||||
test('analytics are not available for non-pro workspaces', async ({ page }) => {
|
||||
const typebotId = cuid()
|
||||
await importTypebotInDatabase(
|
||||
getTestAsset('typebots/results/submissionHeader.json'),
|
||||
{
|
||||
id: typebotId,
|
||||
workspaceId: starterWorkspaceId,
|
||||
}
|
||||
)
|
||||
await injectFakeResults({ typebotId, count: 10 })
|
||||
await page.goto(`/typebots/${typebotId}/results/analytics`)
|
||||
const firstDropoffBox = page.locator('text="%" >> nth=0')
|
||||
await firstDropoffBox.hover()
|
||||
await expect(
|
||||
page.locator('text="Unlock Drop-off rate by upgrading to Pro plan"')
|
||||
).toBeVisible()
|
||||
await firstDropoffBox.click()
|
||||
await expect(
|
||||
page.locator(
|
||||
'text="You need to upgrade your plan in order to unlock in-depth analytics"'
|
||||
)
|
||||
).toBeVisible()
|
||||
})
|
@ -0,0 +1,63 @@
|
||||
import { Flex, Spinner, useDisclosure } from '@chakra-ui/react'
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { useTypebot } from '@/features/editor'
|
||||
import { Stats } from 'models'
|
||||
import React from 'react'
|
||||
import { useAnswersCount } from '../hooks/useAnswersCount'
|
||||
import {
|
||||
Graph,
|
||||
GraphProvider,
|
||||
GroupsCoordinatesProvider,
|
||||
} from '@/features/graph'
|
||||
import { ChangePlanModal, LimitReached } from '@/features/billing'
|
||||
import { StatsCards } from './StatsCards'
|
||||
|
||||
export const AnalyticsGraphContainer = ({ stats }: { stats?: Stats }) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const { typebot, publishedTypebot } = useTypebot()
|
||||
const { showToast } = useToast()
|
||||
const { answersCounts } = useAnswersCount({
|
||||
typebotId: publishedTypebot && typebot?.id,
|
||||
onError: (err) => showToast({ title: err.name, description: err.message }),
|
||||
})
|
||||
return (
|
||||
<Flex
|
||||
w="full"
|
||||
pos="relative"
|
||||
bgColor="gray.50"
|
||||
h="full"
|
||||
justifyContent="center"
|
||||
>
|
||||
{publishedTypebot && answersCounts && stats ? (
|
||||
<GraphProvider isReadOnly>
|
||||
<GroupsCoordinatesProvider groups={publishedTypebot?.groups}>
|
||||
<Graph
|
||||
flex="1"
|
||||
typebot={publishedTypebot}
|
||||
onUnlockProPlanClick={onOpen}
|
||||
answersCounts={[
|
||||
{ ...answersCounts[0], totalAnswers: stats?.totalStarts },
|
||||
...answersCounts?.slice(1),
|
||||
]}
|
||||
/>
|
||||
</GroupsCoordinatesProvider>
|
||||
</GraphProvider>
|
||||
) : (
|
||||
<Flex
|
||||
justify="center"
|
||||
align="center"
|
||||
boxSize="full"
|
||||
bgColor="rgba(255, 255, 255, 0.5)"
|
||||
>
|
||||
<Spinner color="gray" />
|
||||
</Flex>
|
||||
)}
|
||||
<ChangePlanModal
|
||||
onClose={onClose}
|
||||
isOpen={isOpen}
|
||||
type={LimitReached.ANALYTICS}
|
||||
/>
|
||||
<StatsCards stats={stats} pos="absolute" top={10} />
|
||||
</Flex>
|
||||
)
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
import {
|
||||
GridProps,
|
||||
SimpleGrid,
|
||||
Skeleton,
|
||||
Stat,
|
||||
StatLabel,
|
||||
StatNumber,
|
||||
} from '@chakra-ui/react'
|
||||
import { Stats } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
export const StatsCards = ({
|
||||
stats,
|
||||
...props
|
||||
}: { stats?: Stats } & GridProps) => {
|
||||
return (
|
||||
<SimpleGrid columns={{ base: 1, md: 3 }} spacing="6" {...props}>
|
||||
<Stat bgColor="white" p="4" rounded="md" boxShadow="md">
|
||||
<StatLabel>Views</StatLabel>
|
||||
{stats ? (
|
||||
<StatNumber>{stats.totalViews}</StatNumber>
|
||||
) : (
|
||||
<Skeleton w="50%" h="10px" mt="2" />
|
||||
)}
|
||||
</Stat>
|
||||
<Stat bgColor="white" p="4" rounded="md" boxShadow="md">
|
||||
<StatLabel>Starts</StatLabel>
|
||||
{stats ? (
|
||||
<StatNumber>{stats.totalStarts}</StatNumber>
|
||||
) : (
|
||||
<Skeleton w="50%" h="10px" mt="2" />
|
||||
)}
|
||||
</Stat>
|
||||
<Stat bgColor="white" p="4" rounded="md" boxShadow="md">
|
||||
<StatLabel>Completion rate</StatLabel>
|
||||
{stats ? (
|
||||
<StatNumber>
|
||||
{Math.round((stats.totalCompleted / stats.totalStarts) * 100)}%
|
||||
</StatNumber>
|
||||
) : (
|
||||
<Skeleton w="50%" h="10px" mt="2" />
|
||||
)}
|
||||
</Stat>
|
||||
</SimpleGrid>
|
||||
)
|
||||
}
|
25
apps/builder/src/features/analytics/hooks/useAnswersCount.ts
Normal file
25
apps/builder/src/features/analytics/hooks/useAnswersCount.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { fetcher } from '@/utils/helpers'
|
||||
import useSWR from 'swr'
|
||||
import { AnswersCount } from '../types'
|
||||
|
||||
export const useAnswersCount = ({
|
||||
typebotId,
|
||||
onError,
|
||||
}: {
|
||||
typebotId?: string
|
||||
onError: (error: Error) => void
|
||||
}) => {
|
||||
const { data, error, mutate } = useSWR<
|
||||
{ answersCounts: AnswersCount[] },
|
||||
Error
|
||||
>(
|
||||
typebotId ? `/api/typebots/${typebotId}/results/answers/count` : null,
|
||||
fetcher
|
||||
)
|
||||
if (error) onError(error)
|
||||
return {
|
||||
answersCounts: data?.answersCounts,
|
||||
isLoading: !error && !data,
|
||||
mutate,
|
||||
}
|
||||
}
|
2
apps/builder/src/features/analytics/index.ts
Normal file
2
apps/builder/src/features/analytics/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { AnalyticsGraphContainer } from './components/AnalyticsGraphContainer'
|
||||
export type { AnswersCount } from './types'
|
1
apps/builder/src/features/analytics/types.ts
Normal file
1
apps/builder/src/features/analytics/types.ts
Normal file
@ -0,0 +1 @@
|
||||
export type AnswersCount = { groupId: string; totalAnswers: number }
|
Reference in New Issue
Block a user