🐛 (limits) Fix storage limit trigger and e2e tests
This commit is contained in:
committed by
Baptiste Arnaud
parent
1e26703ad4
commit
30dff2d5d7
@@ -2,7 +2,10 @@ import { Flex, Spinner, useDisclosure } 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 { useToast } from 'components/shared/hooks/useToast'
|
import { useToast } from 'components/shared/hooks/useToast'
|
||||||
import { ChangePlanModal } from 'components/shared/modals/ChangePlanModal'
|
import {
|
||||||
|
ChangePlanModal,
|
||||||
|
LimitReached,
|
||||||
|
} from 'components/shared/modals/ChangePlanModal'
|
||||||
import { GraphProvider, GroupsCoordinatesProvider } from 'contexts/GraphContext'
|
import { GraphProvider, GroupsCoordinatesProvider } from 'contexts/GraphContext'
|
||||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||||
import { Stats } from 'models'
|
import { Stats } from 'models'
|
||||||
@@ -49,7 +52,11 @@ export const AnalyticsContent = ({ stats }: { stats?: Stats }) => {
|
|||||||
<Spinner color="gray" />
|
<Spinner color="gray" />
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
<ChangePlanModal onClose={onClose} isOpen={isOpen} />
|
<ChangePlanModal
|
||||||
|
onClose={onClose}
|
||||||
|
isOpen={isOpen}
|
||||||
|
type={LimitReached.ANALYTICS}
|
||||||
|
/>
|
||||||
<StatsCards stats={stats} pos="absolute" top={10} />
|
<StatsCards stats={stats} pos="absolute" top={10} />
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Button, HStack, Tag, useDisclosure, Text } from '@chakra-ui/react'
|
import { Button, HStack, useDisclosure, Text } from '@chakra-ui/react'
|
||||||
import { FolderPlusIcon } from 'assets/icons'
|
import { FolderPlusIcon } from 'assets/icons'
|
||||||
import {
|
import {
|
||||||
LimitReached,
|
LimitReached,
|
||||||
ChangePlanModal,
|
ChangePlanModal,
|
||||||
} from 'components/shared/modals/ChangePlanModal'
|
} from 'components/shared/modals/ChangePlanModal'
|
||||||
|
import { PlanTag } from 'components/shared/PlanTag'
|
||||||
import { useWorkspace } from 'contexts/WorkspaceContext'
|
import { useWorkspace } from 'contexts/WorkspaceContext'
|
||||||
|
import { Plan } from 'db'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { isFreePlan } from 'services/workspace'
|
import { isFreePlan } from 'services/workspace'
|
||||||
|
|
||||||
@@ -26,7 +28,7 @@ export const CreateFolderButton = ({ isLoading, onClick }: Props) => {
|
|||||||
>
|
>
|
||||||
<HStack>
|
<HStack>
|
||||||
<Text>Create a folder</Text>
|
<Text>Create a folder</Text>
|
||||||
{isFreePlan(workspace) && <Tag colorScheme="orange">Pro</Tag>}
|
{isFreePlan(workspace) && <PlanTag plan={Plan.STARTER} />}
|
||||||
</HStack>
|
</HStack>
|
||||||
<ChangePlanModal
|
<ChangePlanModal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const useUsage = (workspaceId?: string) => {
|
|||||||
{ totalChatsUsed: number; totalStorageUsed: number },
|
{ totalChatsUsed: number; totalStorageUsed: number },
|
||||||
Error
|
Error
|
||||||
>(workspaceId ? `/api/workspaces/${workspaceId}/usage` : null, fetcher, {
|
>(workspaceId ? `/api/workspaces/${workspaceId}/usage` : null, fetcher, {
|
||||||
dedupingInterval: env('E2E_TEST') === 'enabled' ? 0 : undefined,
|
dedupingInterval: env('E2E_TEST') === 'true' ? 0 : undefined,
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const useInvoicesQuery = (stripeId?: string | null) => {
|
|||||||
stripeId ? `/api/stripe/invoices?stripeId=${stripeId}` : null,
|
stripeId ? `/api/stripe/invoices?stripeId=${stripeId}` : null,
|
||||||
fetcher,
|
fetcher,
|
||||||
{
|
{
|
||||||
dedupingInterval: env('E2E_TEST') === 'enabled' ? 0 : undefined,
|
dedupingInterval: env('E2E_TEST') === 'true' ? 0 : undefined,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const MembersList = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const handleDeleteMemberClick = (memberId: string) => async () => {
|
const handleDeleteMemberClick = (memberId: string) => async () => {
|
||||||
if (!workspace || !members || !invitations) return
|
if (!workspace) return
|
||||||
await deleteMember(workspace.id, memberId)
|
await deleteMember(workspace.id, memberId)
|
||||||
mutate({
|
mutate({
|
||||||
members: members.filter((m) => m.userId !== memberId),
|
members: members.filter((m) => m.userId !== memberId),
|
||||||
@@ -34,7 +34,7 @@ export const MembersList = () => {
|
|||||||
|
|
||||||
const handleSelectNewRole =
|
const handleSelectNewRole =
|
||||||
(memberId: string) => async (role: WorkspaceRole) => {
|
(memberId: string) => async (role: WorkspaceRole) => {
|
||||||
if (!workspace || !members || !invitations) return
|
if (!workspace) return
|
||||||
await updateMember(workspace.id, { userId: memberId, role })
|
await updateMember(workspace.id, { userId: memberId, role })
|
||||||
mutate({
|
mutate({
|
||||||
members: members.map((m) =>
|
members: members.map((m) =>
|
||||||
@@ -45,7 +45,7 @@ export const MembersList = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDeleteInvitationClick = (id: string) => async () => {
|
const handleDeleteInvitationClick = (id: string) => async () => {
|
||||||
if (!workspace || !members || !invitations) return
|
if (!workspace) return
|
||||||
await deleteInvitation({ workspaceId: workspace.id, id })
|
await deleteInvitation({ workspaceId: workspace.id, id })
|
||||||
mutate({
|
mutate({
|
||||||
invitations: invitations.filter((i) => i.id !== id),
|
invitations: invitations.filter((i) => i.id !== id),
|
||||||
@@ -55,7 +55,7 @@ export const MembersList = () => {
|
|||||||
|
|
||||||
const handleSelectNewInvitationRole =
|
const handleSelectNewInvitationRole =
|
||||||
(id: string) => async (type: WorkspaceRole) => {
|
(id: string) => async (type: WorkspaceRole) => {
|
||||||
if (!workspace || !members || !invitations) return
|
if (!workspace) return
|
||||||
await updateInvitation({ workspaceId: workspace.id, id, type })
|
await updateInvitation({ workspaceId: workspace.id, id, type })
|
||||||
mutate({
|
mutate({
|
||||||
invitations: invitations.map((i) => (i.id === id ? { ...i, type } : i)),
|
invitations: invitations.map((i) => (i.id === id ? { ...i, type } : i)),
|
||||||
@@ -63,17 +63,15 @@ export const MembersList = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleNewInvitation = (invitation: WorkspaceInvitation) => {
|
const handleNewInvitation = async (invitation: WorkspaceInvitation) => {
|
||||||
if (!members || !invitations) return
|
await mutate({
|
||||||
mutate({
|
|
||||||
members,
|
members,
|
||||||
invitations: [...invitations, invitation],
|
invitations: [...invitations, invitation],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleNewMember = (member: Member) => {
|
const handleNewMember = async (member: Member) => {
|
||||||
if (!members || !invitations) return
|
await mutate({
|
||||||
mutate({
|
|
||||||
members: [...members, member],
|
members: [...members, member],
|
||||||
invitations,
|
invitations,
|
||||||
})
|
})
|
||||||
@@ -81,7 +79,7 @@ export const MembersList = () => {
|
|||||||
|
|
||||||
const canInviteNewMember = checkCanInviteMember({
|
const canInviteNewMember = checkCanInviteMember({
|
||||||
plan: workspace?.plan,
|
plan: workspace?.plan,
|
||||||
currentMembersCount: [...(members ?? []), ...(invitations ?? [])].length,
|
currentMembersCount: [...members, ...invitations].length,
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -103,7 +101,7 @@ export const MembersList = () => {
|
|||||||
isLocked={!canInviteNewMember}
|
isLocked={!canInviteNewMember}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{members?.map((member) => (
|
{members.map((member) => (
|
||||||
<MemberItem
|
<MemberItem
|
||||||
key={member.userId}
|
key={member.userId}
|
||||||
email={member.email ?? ''}
|
email={member.email ?? ''}
|
||||||
@@ -116,7 +114,7 @@ export const MembersList = () => {
|
|||||||
canEdit={canEdit}
|
canEdit={canEdit}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{invitations?.map((invitation) => (
|
{invitations.map((invitation) => (
|
||||||
<MemberItem
|
<MemberItem
|
||||||
key={invitation.email}
|
key={invitation.email}
|
||||||
email={invitation.email ?? ''}
|
email={invitation.email ?? ''}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { HStack, Tag, Text, Tooltip } from '@chakra-ui/react'
|
import { HStack, Text, Tooltip } from '@chakra-ui/react'
|
||||||
|
import { PlanTag } from 'components/shared/PlanTag'
|
||||||
import { useWorkspace } from 'contexts/WorkspaceContext'
|
import { useWorkspace } from 'contexts/WorkspaceContext'
|
||||||
|
import { Plan } from 'db'
|
||||||
import {
|
import {
|
||||||
BubbleBlockType,
|
BubbleBlockType,
|
||||||
InputBlockType,
|
InputBlockType,
|
||||||
@@ -52,11 +54,7 @@ export const BlockTypeLabel = ({ type }: Props): JSX.Element => {
|
|||||||
<Tooltip label="Upload Files">
|
<Tooltip label="Upload Files">
|
||||||
<HStack>
|
<HStack>
|
||||||
<Text>File</Text>
|
<Text>File</Text>
|
||||||
{isFreePlan(workspace) && (
|
{isFreePlan(workspace) && <PlanTag plan={Plan.STARTER} />}
|
||||||
<Tag colorScheme="orange" size="sm">
|
|
||||||
Pro
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
</HStack>
|
</HStack>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
|
import { Flex, FormLabel, Stack, Switch, useDisclosure } from '@chakra-ui/react'
|
||||||
import {
|
import {
|
||||||
Flex,
|
ChangePlanModal,
|
||||||
FormLabel,
|
LimitReached,
|
||||||
Stack,
|
} from 'components/shared/modals/ChangePlanModal'
|
||||||
Switch,
|
import { PlanTag } from 'components/shared/PlanTag'
|
||||||
Tag,
|
|
||||||
useDisclosure,
|
|
||||||
} from '@chakra-ui/react'
|
|
||||||
import { ChangePlanModal } from 'components/shared/modals/ChangePlanModal'
|
|
||||||
import { SwitchWithLabel } from 'components/shared/SwitchWithLabel'
|
import { SwitchWithLabel } from 'components/shared/SwitchWithLabel'
|
||||||
import { useWorkspace } from 'contexts/WorkspaceContext'
|
import { useWorkspace } from 'contexts/WorkspaceContext'
|
||||||
|
import { Plan } from 'db'
|
||||||
import { GeneralSettings } from 'models'
|
import { GeneralSettings } from 'models'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { isFreePlan } from 'services/workspace'
|
import { isFreePlan } from 'services/workspace'
|
||||||
@@ -25,9 +23,9 @@ export const GeneralSettingsForm = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||||
const { workspace } = useWorkspace()
|
const { workspace } = useWorkspace()
|
||||||
const isUserFreePlan = isFreePlan(workspace)
|
const isWorkspaceFreePlan = isFreePlan(workspace)
|
||||||
const handleSwitchChange = () => {
|
const handleSwitchChange = () => {
|
||||||
if (generalSettings?.isBrandingEnabled && isUserFreePlan) return
|
if (generalSettings?.isBrandingEnabled && isWorkspaceFreePlan) return
|
||||||
onGeneralSettingsChange({
|
onGeneralSettingsChange({
|
||||||
...generalSettings,
|
...generalSettings,
|
||||||
isBrandingEnabled: !generalSettings?.isBrandingEnabled,
|
isBrandingEnabled: !generalSettings?.isBrandingEnabled,
|
||||||
@@ -56,15 +54,19 @@ export const GeneralSettingsForm = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={6}>
|
<Stack spacing={6}>
|
||||||
<ChangePlanModal isOpen={isOpen} onClose={onClose} />
|
<ChangePlanModal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
type={LimitReached.BRAND}
|
||||||
|
/>
|
||||||
<Flex
|
<Flex
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
align="center"
|
align="center"
|
||||||
onClick={isUserFreePlan ? onOpen : undefined}
|
onClick={isWorkspaceFreePlan ? onOpen : undefined}
|
||||||
>
|
>
|
||||||
<FormLabel htmlFor="branding" mb="0">
|
<FormLabel htmlFor="branding" mb="0">
|
||||||
Typebot.io branding{' '}
|
Typebot.io branding{' '}
|
||||||
{isUserFreePlan && <Tag colorScheme="orange">Pro</Tag>}
|
{isWorkspaceFreePlan && <PlanTag plan={Plan.STARTER} />}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<Switch
|
<Switch
|
||||||
id="branding"
|
id="branding"
|
||||||
|
|||||||
@@ -36,11 +36,10 @@ export const EditableUrl = ({
|
|||||||
<Tooltip label="Edit">
|
<Tooltip label="Edit">
|
||||||
<EditablePreview
|
<EditablePreview
|
||||||
mx={1}
|
mx={1}
|
||||||
bgColor="blue.500"
|
borderWidth="1px"
|
||||||
color="white"
|
|
||||||
px={3}
|
px={3}
|
||||||
rounded="md"
|
rounded="md"
|
||||||
cursor="pointer"
|
cursor="text"
|
||||||
display="flex"
|
display="flex"
|
||||||
fontWeight="semibold"
|
fontWeight="semibold"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,18 +4,20 @@ import {
|
|||||||
HStack,
|
HStack,
|
||||||
IconButton,
|
IconButton,
|
||||||
Stack,
|
Stack,
|
||||||
Tag,
|
|
||||||
Wrap,
|
Wrap,
|
||||||
Text,
|
Text,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { TrashIcon } from 'assets/icons'
|
import { TrashIcon } from 'assets/icons'
|
||||||
import { UpgradeButton } from 'components/shared/buttons/UpgradeButton'
|
import { UpgradeButton } from 'components/shared/buttons/UpgradeButton'
|
||||||
import { useToast } from 'components/shared/hooks/useToast'
|
import { useToast } from 'components/shared/hooks/useToast'
|
||||||
|
import { LimitReached } from 'components/shared/modals/ChangePlanModal'
|
||||||
|
import { PlanTag } from 'components/shared/PlanTag'
|
||||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||||
import { useWorkspace } from 'contexts/WorkspaceContext'
|
import { useWorkspace } from 'contexts/WorkspaceContext'
|
||||||
|
import { Plan } from 'db'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { parseDefaultPublicId } from 'services/typebots'
|
import { parseDefaultPublicId } from 'services/typebots'
|
||||||
import { isFreePlan } from 'services/workspace'
|
import { isWorkspaceProPlan } from 'services/workspace'
|
||||||
import { getViewerUrl, isDefined, isNotDefined } from 'utils'
|
import { getViewerUrl, isDefined, isNotDefined } from 'utils'
|
||||||
import { CustomDomainsDropdown } from './customDomain/CustomDomainsDropdown'
|
import { CustomDomainsDropdown } from './customDomain/CustomDomainsDropdown'
|
||||||
import { EditableUrl } from './EditableUrl'
|
import { EditableUrl } from './EditableUrl'
|
||||||
@@ -80,19 +82,18 @@ export const ShareContent = () => {
|
|||||||
/>
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
)}
|
)}
|
||||||
{isFreePlan(workspace) ? (
|
{isWorkspaceProPlan(workspace) &&
|
||||||
<UpgradeButton colorScheme="gray">
|
isNotDefined(typebot?.customDomain) ? (
|
||||||
<Text mr="2">Add my domain</Text>{' '}
|
<CustomDomainsDropdown
|
||||||
<Tag colorScheme="orange">Pro</Tag>
|
onCustomDomainSelect={handleCustomDomainChange}
|
||||||
</UpgradeButton>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<UpgradeButton
|
||||||
{isNotDefined(typebot?.customDomain) && (
|
colorScheme="gray"
|
||||||
<CustomDomainsDropdown
|
limitReachedType={LimitReached.CUSTOM_DOMAIN}
|
||||||
onCustomDomainSelect={handleCustomDomainChange}
|
>
|
||||||
/>
|
<Text mr="2">Add my domain</Text> <PlanTag plan={Plan.PRO} />
|
||||||
)}
|
</UpgradeButton>
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
computeSourceCoordinates,
|
computeSourceCoordinates,
|
||||||
computeDropOffPath,
|
computeDropOffPath,
|
||||||
} from 'services/graph'
|
} from 'services/graph'
|
||||||
import { isFreePlan } from 'services/workspace'
|
import { isWorkspaceProPlan } from 'services/workspace'
|
||||||
import { byId, isDefined } from 'utils'
|
import { byId, isDefined } from 'utils'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -28,7 +28,7 @@ export const DropOffEdge = ({
|
|||||||
const { sourceEndpoints, graphPosition } = useGraph()
|
const { sourceEndpoints, graphPosition } = useGraph()
|
||||||
const { publishedTypebot } = useTypebot()
|
const { publishedTypebot } = useTypebot()
|
||||||
|
|
||||||
const isUserOnFreePlan = isFreePlan(workspace)
|
const isProPlan = isWorkspaceProPlan(workspace)
|
||||||
|
|
||||||
const totalAnswers = useMemo(
|
const totalAnswers = useMemo(
|
||||||
() => answersCounts.find((a) => a.groupId === groupId)?.totalAnswers,
|
() => answersCounts.find((a) => a.groupId === groupId)?.totalAnswers,
|
||||||
@@ -95,7 +95,7 @@ export const DropOffEdge = ({
|
|||||||
>
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label="Unlock Drop-off rate by upgrading to Pro plan"
|
label="Unlock Drop-off rate by upgrading to Pro plan"
|
||||||
isDisabled={!isUserOnFreePlan}
|
isDisabled={isProPlan}
|
||||||
>
|
>
|
||||||
<VStack
|
<VStack
|
||||||
bgColor={'red.500'}
|
bgColor={'red.500'}
|
||||||
@@ -105,13 +105,28 @@ export const DropOffEdge = ({
|
|||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
w="full"
|
w="full"
|
||||||
h="full"
|
h="full"
|
||||||
filter={isUserOnFreePlan ? 'blur(4px)' : ''}
|
onClick={isProPlan ? undefined : onUnlockProPlanClick}
|
||||||
onClick={isUserOnFreePlan ? onUnlockProPlanClick : undefined}
|
cursor={isProPlan ? 'auto' : 'pointer'}
|
||||||
cursor={isUserOnFreePlan ? 'pointer' : 'auto'}
|
|
||||||
>
|
>
|
||||||
<Text>{isUserOnFreePlan ? 'X' : dropOffRate}%</Text>
|
<Text filter={isProPlan ? '' : 'blur(2px)'}>
|
||||||
|
{isProPlan ? (
|
||||||
|
dropOffRate
|
||||||
|
) : (
|
||||||
|
<Text as="span" filter="blur(2px)">
|
||||||
|
X
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
%
|
||||||
|
</Text>
|
||||||
<Tag colorScheme="red">
|
<Tag colorScheme="red">
|
||||||
{isUserOnFreePlan ? 'n' : totalDroppedUser} users
|
{isProPlan ? (
|
||||||
|
totalDroppedUser
|
||||||
|
) : (
|
||||||
|
<Text as="span" filter="blur(3px)" mr="1">
|
||||||
|
NN
|
||||||
|
</Text>
|
||||||
|
)}{' '}
|
||||||
|
users
|
||||||
</Tag>
|
</Tag>
|
||||||
</VStack>
|
</VStack>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ export const Edges = ({
|
|||||||
pos="absolute"
|
pos="absolute"
|
||||||
left="0"
|
left="0"
|
||||||
top="0"
|
top="0"
|
||||||
pointerEvents="none"
|
|
||||||
shapeRendering="geometricPrecision"
|
shapeRendering="geometricPrecision"
|
||||||
>
|
>
|
||||||
<DrawingEdge />
|
<DrawingEdge />
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Tag } from '@chakra-ui/react'
|
import { Tag, TagProps } from '@chakra-ui/react'
|
||||||
import { Plan } from 'db'
|
import { Plan } from 'db'
|
||||||
|
|
||||||
export const PlanTag = ({ plan }: { plan?: Plan }) => {
|
export const PlanTag = ({ plan, ...props }: { plan?: Plan } & TagProps) => {
|
||||||
switch (plan) {
|
switch (plan) {
|
||||||
case Plan.LIFETIME:
|
case Plan.LIFETIME:
|
||||||
case Plan.PRO: {
|
case Plan.PRO: {
|
||||||
return (
|
return (
|
||||||
<Tag colorScheme="blue" data-testid="plan-tag">
|
<Tag colorScheme="blue" data-testid="pro-plan-tag" {...props}>
|
||||||
Pro
|
Pro
|
||||||
</Tag>
|
</Tag>
|
||||||
)
|
)
|
||||||
@@ -14,14 +14,14 @@ export const PlanTag = ({ plan }: { plan?: Plan }) => {
|
|||||||
case Plan.OFFERED:
|
case Plan.OFFERED:
|
||||||
case Plan.STARTER: {
|
case Plan.STARTER: {
|
||||||
return (
|
return (
|
||||||
<Tag colorScheme="orange" data-testid="plan-tag">
|
<Tag colorScheme="orange" data-testid="starter-plan-tag" {...props}>
|
||||||
Starter
|
Starter
|
||||||
</Tag>
|
</Tag>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return (
|
return (
|
||||||
<Tag colorScheme="gray" data-testid="plan-tag">
|
<Tag colorScheme="gray" data-testid="free-plan-tag" {...props}>
|
||||||
Free
|
Free
|
||||||
</Tag>
|
</Tag>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { isNotDefined } from 'utils'
|
|||||||
import { ChangePlanModal } from '../modals/ChangePlanModal'
|
import { ChangePlanModal } from '../modals/ChangePlanModal'
|
||||||
import { LimitReached } from '../modals/ChangePlanModal'
|
import { LimitReached } from '../modals/ChangePlanModal'
|
||||||
|
|
||||||
type Props = { type?: LimitReached } & ButtonProps
|
type Props = { limitReachedType?: LimitReached } & ButtonProps
|
||||||
|
|
||||||
export const UpgradeButton = ({ type, ...props }: Props) => {
|
export const UpgradeButton = ({ limitReachedType, ...props }: Props) => {
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||||
const { workspace } = useWorkspace()
|
const { workspace } = useWorkspace()
|
||||||
return (
|
return (
|
||||||
@@ -18,7 +18,11 @@ export const UpgradeButton = ({ type, ...props }: Props) => {
|
|||||||
onClick={onOpen}
|
onClick={onOpen}
|
||||||
>
|
>
|
||||||
{props.children ?? 'Upgrade'}
|
{props.children ?? 'Upgrade'}
|
||||||
<ChangePlanModal isOpen={isOpen} onClose={onClose} type={type} />
|
<ChangePlanModal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
type={limitReachedType}
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ import { ChangePlanForm } from 'components/shared/ChangePlanForm'
|
|||||||
|
|
||||||
export enum LimitReached {
|
export enum LimitReached {
|
||||||
BRAND = 'remove branding',
|
BRAND = 'remove branding',
|
||||||
CUSTOM_DOMAIN = 'add custom domain',
|
CUSTOM_DOMAIN = 'add custom domains',
|
||||||
FOLDER = 'create folders',
|
FOLDER = 'create folders',
|
||||||
FILE_INPUT = 'use file input blocks',
|
FILE_INPUT = 'use file input blocks',
|
||||||
|
ANALYTICS = 'unlock in-depth analytics',
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChangePlanModalProps = {
|
type ChangePlanModalProps = {
|
||||||
|
|||||||
@@ -37,7 +37,11 @@ const workspaceContext = createContext<{
|
|||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
export const WorkspaceContext = ({ children }: { children: ReactNode }) => {
|
type WorkspaceContextProps = {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WorkspaceContext = ({ children }: WorkspaceContextProps) => {
|
||||||
const { query } = useRouter()
|
const { query } = useRouter()
|
||||||
const { user } = useUser()
|
const { user } = useUser()
|
||||||
const userId = user?.id
|
const userId = user?.id
|
||||||
@@ -45,6 +49,7 @@ export const WorkspaceContext = ({ children }: { children: ReactNode }) => {
|
|||||||
const { workspaces, isLoading, mutate } = useWorkspaces({ userId })
|
const { workspaces, isLoading, mutate } = useWorkspaces({ userId })
|
||||||
const [currentWorkspace, setCurrentWorkspace] =
|
const [currentWorkspace, setCurrentWorkspace] =
|
||||||
useState<WorkspaceWithMembers>()
|
useState<WorkspaceWithMembers>()
|
||||||
|
const [pendingWorkspaceId, setPendingWorkspaceId] = useState<string>()
|
||||||
|
|
||||||
const canEdit =
|
const canEdit =
|
||||||
workspaces
|
workspaces
|
||||||
@@ -58,7 +63,9 @@ export const WorkspaceContext = ({ children }: { children: ReactNode }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!workspaces || workspaces.length === 0 || currentWorkspace) return
|
if (!workspaces || workspaces.length === 0 || currentWorkspace) return
|
||||||
const lastWorspaceId =
|
const lastWorspaceId =
|
||||||
query.workspaceId?.toString() ?? localStorage.getItem('workspaceId')
|
pendingWorkspaceId ??
|
||||||
|
query.workspaceId?.toString() ??
|
||||||
|
localStorage.getItem('workspaceId')
|
||||||
const defaultWorkspace = lastWorspaceId
|
const defaultWorkspace = lastWorspaceId
|
||||||
? workspaces.find(byId(lastWorspaceId))
|
? workspaces.find(byId(lastWorspaceId))
|
||||||
: workspaces.find((w) =>
|
: workspaces.find((w) =>
|
||||||
@@ -77,11 +84,8 @@ export const WorkspaceContext = ({ children }: { children: ReactNode }) => {
|
|||||||
}, [currentWorkspace?.id])
|
}, [currentWorkspace?.id])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (!currentWorkspace) return setPendingWorkspaceId(typebot?.workspaceId)
|
||||||
!typebot?.workspaceId ||
|
if (!typebot?.workspaceId || typebot.workspaceId === currentWorkspace.id)
|
||||||
!currentWorkspace ||
|
|
||||||
typebot.workspaceId === currentWorkspace.id
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
switchWorkspace(typebot.workspaceId)
|
switchWorkspace(typebot.workspaceId)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|||||||
@@ -38,6 +38,17 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
userId: existingUser.id,
|
userId: existingUser.id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
if (env('E2E_TEST') !== 'true')
|
||||||
|
await sendEmailNotification({
|
||||||
|
to: data.email,
|
||||||
|
subject: "You've been invited to collaborate 🤝",
|
||||||
|
html: workspaceMemberInvitationEmail({
|
||||||
|
workspaceName: workspace.name,
|
||||||
|
guestEmail: data.email,
|
||||||
|
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspace.id}`,
|
||||||
|
hostEmail: user.email ?? '',
|
||||||
|
}),
|
||||||
|
})
|
||||||
return res.send({
|
return res.send({
|
||||||
member: {
|
member: {
|
||||||
userId: existingUser.id,
|
userId: existingUser.id,
|
||||||
@@ -47,19 +58,21 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
workspaceId: data.workspaceId,
|
workspaceId: data.workspaceId,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else await prisma.workspaceInvitation.create({ data })
|
} else {
|
||||||
if (env('E2E_TEST') !== 'true')
|
const invitation = await prisma.workspaceInvitation.create({ data })
|
||||||
await sendEmailNotification({
|
if (env('E2E_TEST') !== 'true')
|
||||||
to: data.email,
|
await sendEmailNotification({
|
||||||
subject: "You've been invited to collaborate 🤝",
|
to: data.email,
|
||||||
html: workspaceMemberInvitationEmail({
|
subject: "You've been invited to collaborate 🤝",
|
||||||
workspaceName: workspace.name,
|
html: workspaceMemberInvitationEmail({
|
||||||
guestEmail: data.email,
|
workspaceName: workspace.name,
|
||||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspace.id}`,
|
guestEmail: data.email,
|
||||||
hostEmail: user.email ?? '',
|
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspace.id}`,
|
||||||
}),
|
hostEmail: user.email ?? '',
|
||||||
})
|
}),
|
||||||
return res.send({ message: 'success' })
|
})
|
||||||
|
return res.send({ invitation })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
methodNotAllowed(res)
|
methodNotAllowed(res)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export const refreshUser = async () => {
|
|||||||
document.dispatchEvent(event)
|
document.dispatchEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const connectedAsOtherUser = async (page: Page) =>
|
export const mockSessionResponsesToOtherUser = async (page: Page) =>
|
||||||
page.route('/api/auth/session', (route) => {
|
page.route('/api/auth/session', (route) => {
|
||||||
if (route.request().method() === 'GET') {
|
if (route.request().method() === 'GET') {
|
||||||
return route.fulfill({
|
return route.fulfill({
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { readFileSync } from 'fs'
|
|||||||
import { injectFakeResults } from 'utils'
|
import { injectFakeResults } from 'utils'
|
||||||
import { encrypt } from 'utils/api'
|
import { encrypt } from 'utils/api'
|
||||||
import Stripe from 'stripe'
|
import Stripe from 'stripe'
|
||||||
|
import cuid from 'cuid'
|
||||||
|
|
||||||
const prisma = new PrismaClient()
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ const stripe = new Stripe(process.env.STRIPE_TEST_SECRET_KEY ?? '', {
|
|||||||
apiVersion: '2022-08-01',
|
apiVersion: '2022-08-01',
|
||||||
})
|
})
|
||||||
|
|
||||||
const userId = 'userId'
|
export const userId = 'userId'
|
||||||
const otherUserId = 'otherUserId'
|
const otherUserId = 'otherUserId'
|
||||||
export const freeWorkspaceId = 'freeWorkspace'
|
export const freeWorkspaceId = 'freeWorkspace'
|
||||||
export const starterWorkspaceId = 'starterWorkspace'
|
export const starterWorkspaceId = 'starterWorkspace'
|
||||||
@@ -49,6 +50,12 @@ export const teardownDatabase = async () => {
|
|||||||
return prisma.webhook.deleteMany()
|
return prisma.webhook.deleteMany()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const deleteWorkspaces = async (workspaceIds: string[]) => {
|
||||||
|
await prisma.workspace.deleteMany({
|
||||||
|
where: { id: { in: workspaceIds } },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const addSubscriptionToWorkspace = async (
|
export const addSubscriptionToWorkspace = async (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
items: Stripe.SubscriptionCreateParams.Item[],
|
items: Stripe.SubscriptionCreateParams.Item[],
|
||||||
@@ -90,12 +97,12 @@ export const addSubscriptionToWorkspace = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const setupDatabase = async () => {
|
export const setupDatabase = async () => {
|
||||||
await createWorkspaces()
|
await setupWorkspaces()
|
||||||
await createUsers()
|
await setupUsers()
|
||||||
return createCredentials()
|
return setupCredentials()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createWorkspaces = async () =>
|
export const setupWorkspaces = async () =>
|
||||||
prisma.workspace.createMany({
|
prisma.workspace.createMany({
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
@@ -122,21 +129,27 @@ export const createWorkspaces = async () =>
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
export const createWorkspace = async (workspace: Partial<Workspace>) => {
|
export const createWorkspaces = async (workspaces: Partial<Workspace>[]) => {
|
||||||
const { id: workspaceId } = await prisma.workspace.create({
|
const workspaceIds = workspaces.map((workspace) => workspace.id ?? cuid())
|
||||||
data: {
|
await prisma.workspace.createMany({
|
||||||
|
data: workspaces.map((workspace, index) => ({
|
||||||
|
id: workspaceIds[index],
|
||||||
name: 'Free workspace',
|
name: 'Free workspace',
|
||||||
plan: Plan.FREE,
|
plan: Plan.FREE,
|
||||||
...workspace,
|
...workspace,
|
||||||
},
|
})),
|
||||||
})
|
})
|
||||||
await prisma.memberInWorkspace.create({
|
await prisma.memberInWorkspace.createMany({
|
||||||
data: { userId, workspaceId, role: WorkspaceRole.ADMIN },
|
data: workspaces.map((_, index) => ({
|
||||||
|
userId,
|
||||||
|
workspaceId: workspaceIds[index],
|
||||||
|
role: WorkspaceRole.ADMIN,
|
||||||
|
})),
|
||||||
})
|
})
|
||||||
return workspaceId
|
return workspaceIds
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createUsers = async () => {
|
export const setupUsers = async () => {
|
||||||
await prisma.user.create({
|
await prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
id: userId,
|
id: userId,
|
||||||
@@ -237,7 +250,7 @@ export const createFolders = (partialFolders: Partial<DashboardFolder>[]) =>
|
|||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
|
|
||||||
const createCredentials = () => {
|
const setupCredentials = () => {
|
||||||
const { encryptedData, iv } = encrypt({
|
const { encryptedData, iv } = encrypt({
|
||||||
expiry_date: 1642441058842,
|
expiry_date: 1642441058842,
|
||||||
access_token:
|
access_token:
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { userId } from 'playwright/services/database'
|
||||||
|
|
||||||
|
test.describe.configure({ mode: 'parallel' })
|
||||||
|
|
||||||
test('should display user info properly', async ({ page }) => {
|
test('should display user info properly', async ({ page }) => {
|
||||||
await page.goto('/typebots')
|
await page.goto('/typebots')
|
||||||
@@ -18,7 +21,7 @@ test('should display user info properly', async ({ page }) => {
|
|||||||
await expect(page.locator('img >> nth=1')).toHaveAttribute(
|
await expect(page.locator('img >> nth=1')).toHaveAttribute(
|
||||||
'src',
|
'src',
|
||||||
new RegExp(
|
new RegExp(
|
||||||
`http://localhost:9000/typebot/public/users/proUser/avatar`,
|
`http://localhost:9000/typebot/public/users/${userId}/avatar`,
|
||||||
'gm'
|
'gm'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
30
apps/builder/playwright/tests/analytics.spec.ts
Normal file
30
apps/builder/playwright/tests/analytics.spec.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import test, { expect } from '@playwright/test'
|
||||||
|
import cuid from 'cuid'
|
||||||
|
import path from 'path'
|
||||||
|
import {
|
||||||
|
importTypebotInDatabase,
|
||||||
|
starterWorkspaceId,
|
||||||
|
} from '../services/database'
|
||||||
|
|
||||||
|
test('analytics are not available for non-pro workspaces', async ({ page }) => {
|
||||||
|
const typebotId = cuid()
|
||||||
|
await importTypebotInDatabase(
|
||||||
|
path.join(__dirname, '../fixtures/typebots/results/submissionHeader.json'),
|
||||||
|
{
|
||||||
|
id: typebotId,
|
||||||
|
workspaceId: starterWorkspaceId,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
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()
|
||||||
|
})
|
||||||
@@ -5,13 +5,34 @@ import {
|
|||||||
addSubscriptionToWorkspace,
|
addSubscriptionToWorkspace,
|
||||||
createResults,
|
createResults,
|
||||||
createTypebots,
|
createTypebots,
|
||||||
createWorkspace,
|
createWorkspaces,
|
||||||
starterWorkspaceId,
|
deleteWorkspaces,
|
||||||
} from '../services/database'
|
} from '../services/database'
|
||||||
|
|
||||||
|
const usageWorkspaceId = cuid()
|
||||||
|
const usageTypebotId = cuid()
|
||||||
|
const planChangeWorkspaceId = cuid()
|
||||||
|
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
await createWorkspaces([
|
||||||
|
{
|
||||||
|
id: usageWorkspaceId,
|
||||||
|
name: 'Usage Workspace',
|
||||||
|
plan: Plan.STARTER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: planChangeWorkspaceId,
|
||||||
|
name: 'Plan Change Workspace',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
await createTypebots([{ id: usageTypebotId, workspaceId: usageWorkspaceId }])
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterAll(async () => {
|
||||||
|
await deleteWorkspaces([usageWorkspaceId, planChangeWorkspaceId])
|
||||||
|
})
|
||||||
|
|
||||||
test('should display valid usage', async ({ page }) => {
|
test('should display valid usage', async ({ page }) => {
|
||||||
const starterTypebotId = cuid()
|
|
||||||
createTypebots([{ id: starterTypebotId, workspaceId: starterWorkspaceId }])
|
|
||||||
await page.goto('/typebots')
|
await page.goto('/typebots')
|
||||||
await page.click('text=Settings & Members')
|
await page.click('text=Settings & Members')
|
||||||
await page.click('text=Billing & Usage')
|
await page.click('text=Billing & Usage')
|
||||||
@@ -24,37 +45,34 @@ test('should display valid usage', async ({ page }) => {
|
|||||||
await page.click('text=Settings & Members')
|
await page.click('text=Settings & Members')
|
||||||
await page.click('text=Billing & Usage')
|
await page.click('text=Billing & Usage')
|
||||||
await expect(page.locator('text="/ 300"')).toBeVisible()
|
await expect(page.locator('text="/ 300"')).toBeVisible()
|
||||||
|
await expect(page.locator('text="Storage"')).toBeHidden()
|
||||||
await page.click('text=Free workspace', { force: true })
|
await page.click('text=Free workspace', { force: true })
|
||||||
|
|
||||||
await createResults({
|
await createResults({
|
||||||
idPrefix: 'usage',
|
|
||||||
count: 10,
|
count: 10,
|
||||||
typebotId: starterTypebotId,
|
typebotId: usageTypebotId,
|
||||||
isChronological: false,
|
|
||||||
fakeStorage: 1100 * 1024 * 1024,
|
fakeStorage: 1100 * 1024 * 1024,
|
||||||
})
|
})
|
||||||
await page.click('text=Free workspace')
|
await page.click('text=Free workspace')
|
||||||
await page.click('text="Starter workspace"')
|
await page.click('text="Usage Workspace"')
|
||||||
await page.click('text=Settings & Members')
|
await page.click('text=Settings & Members')
|
||||||
await page.click('text=Billing & Usage')
|
await page.click('text=Billing & Usage')
|
||||||
await expect(page.locator('text="/ 2,000"')).toBeVisible()
|
await expect(page.locator('text="/ 2,000"')).toBeVisible()
|
||||||
await expect(page.locator('text="/ 2 GB"')).toBeVisible()
|
await expect(page.locator('text="/ 2 GB"')).toBeVisible()
|
||||||
await expect(page.locator('text="1.07 GB"')).toBeVisible()
|
await expect(page.locator('text="10" >> nth=0')).toBeVisible()
|
||||||
await expect(page.locator('text="200"')).toBeVisible()
|
|
||||||
await expect(page.locator('[role="progressbar"] >> nth=0')).toHaveAttribute(
|
await expect(page.locator('[role="progressbar"] >> nth=0')).toHaveAttribute(
|
||||||
'aria-valuenow',
|
'aria-valuenow',
|
||||||
'10'
|
'1'
|
||||||
)
|
)
|
||||||
|
await expect(page.locator('text="1.07 GB"')).toBeVisible()
|
||||||
await expect(page.locator('[role="progressbar"] >> nth=1')).toHaveAttribute(
|
await expect(page.locator('[role="progressbar"] >> nth=1')).toHaveAttribute(
|
||||||
'aria-valuenow',
|
'aria-valuenow',
|
||||||
'54'
|
'54'
|
||||||
)
|
)
|
||||||
|
|
||||||
await createResults({
|
await createResults({
|
||||||
idPrefix: 'usage2',
|
typebotId: usageTypebotId,
|
||||||
typebotId: starterTypebotId,
|
count: 1090,
|
||||||
isChronological: false,
|
|
||||||
count: 900,
|
|
||||||
fakeStorage: 1200 * 1024 * 1024,
|
fakeStorage: 1200 * 1024 * 1024,
|
||||||
})
|
})
|
||||||
await page.click('text="Settings"')
|
await page.click('text="Settings"')
|
||||||
@@ -68,12 +86,11 @@ test('should display valid usage', async ({ page }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('plan changes should work', async ({ page }) => {
|
test('plan changes should work', async ({ page }) => {
|
||||||
const workspaceId = await createWorkspace({ name: 'Awesome workspace' })
|
test.setTimeout(80000)
|
||||||
|
|
||||||
// Upgrade to STARTER
|
// Upgrade to STARTER
|
||||||
await page.goto('/typebots')
|
await page.goto('/typebots')
|
||||||
await page.click('text=Pro workspace')
|
await page.click('text=Pro workspace')
|
||||||
await page.click('text=Awesome workspace')
|
await page.click('text=Plan Change Workspace')
|
||||||
await page.click('text=Settings & Members')
|
await page.click('text=Settings & Members')
|
||||||
await page.click('text=Billing & Usage')
|
await page.click('text=Billing & Usage')
|
||||||
await page.click('button >> text="2,000"')
|
await page.click('button >> text="2,000"')
|
||||||
@@ -89,7 +106,7 @@ test('plan changes should work', async ({ page }) => {
|
|||||||
await expect(page.locator('text=$4.00 >> nth=0')).toBeVisible()
|
await expect(page.locator('text=$4.00 >> nth=0')).toBeVisible()
|
||||||
await expect(page.locator('text=user@email.com')).toBeVisible()
|
await expect(page.locator('text=user@email.com')).toBeVisible()
|
||||||
await addSubscriptionToWorkspace(
|
await addSubscriptionToWorkspace(
|
||||||
workspaceId,
|
planChangeWorkspaceId,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
price: process.env.STRIPE_STARTER_PRICE_ID,
|
price: process.env.STRIPE_STARTER_PRICE_ID,
|
||||||
@@ -148,26 +165,23 @@ test('plan changes should work', async ({ page }) => {
|
|||||||
await page.goto('/typebots')
|
await page.goto('/typebots')
|
||||||
await page.click('text=Settings & Members')
|
await page.click('text=Settings & Members')
|
||||||
await page.click('text=Billing & Usage')
|
await page.click('text=Billing & Usage')
|
||||||
await expect(page.locator('[data-testid="plan-tag"]')).toHaveText('Pro')
|
await expect(page.locator('[data-testid="pro-plan-tag"]')).toBeVisible()
|
||||||
await page.click('button >> text="Cancel my subscription"')
|
await page.click('button >> text="Cancel my subscription"')
|
||||||
await expect(page.locator('[data-testid="plan-tag"]')).toHaveText('Free')
|
await expect(page.locator('[data-testid="free-plan-tag"]')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should display invoices', async ({ page }) => {
|
test('should display invoices', async ({ page }) => {
|
||||||
await page.goto('/typebots')
|
await page.goto('/typebots')
|
||||||
await page.click('text=Settings & Members')
|
await page.click('text=Settings & Members')
|
||||||
await page.click('text=Billing & Usage')
|
await page.click('text=Billing & Usage')
|
||||||
await expect(
|
await expect(page.locator('text="Invoices"')).toBeHidden()
|
||||||
page.locator('text="No invoices found for this workspace."')
|
|
||||||
).toBeVisible()
|
|
||||||
await page.click('text=Pro workspace', { force: true })
|
await page.click('text=Pro workspace', { force: true })
|
||||||
|
|
||||||
await page.click('text=Pro workspace')
|
await page.click('text=Pro workspace')
|
||||||
await page.click('text=Starter workspace')
|
await page.click('text=Plan Change Workspace')
|
||||||
await page.click('text=Settings & Members')
|
await page.click('text=Settings & Members')
|
||||||
await page.click('text=Billing & Usage')
|
await page.click('text=Billing & Usage')
|
||||||
await expect(page.locator('text="Invoices"')).toBeVisible()
|
await expect(page.locator('text="Invoices"')).toBeVisible()
|
||||||
await expect(page.locator('text="Wed Jun 01 2022"')).toBeVisible()
|
await expect(page.locator('tr')).toHaveCount(2)
|
||||||
await expect(page.locator('text="74567541-0001"')).toBeVisible()
|
await expect(page.locator('text="€39.00"')).toBeVisible()
|
||||||
await expect(page.locator('text="€30.00" >> nth=0')).toBeVisible()
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
createResults,
|
createResults,
|
||||||
createTypebots,
|
createTypebots,
|
||||||
parseDefaultGroupWithBlock,
|
parseDefaultGroupWithBlock,
|
||||||
|
userId,
|
||||||
} from '../services/database'
|
} from '../services/database'
|
||||||
|
|
||||||
test.describe('Typebot owner', () => {
|
test.describe('Typebot owner', () => {
|
||||||
@@ -21,7 +22,7 @@ test.describe('Typebot owner', () => {
|
|||||||
plan: Plan.FREE,
|
plan: Plan.FREE,
|
||||||
members: {
|
members: {
|
||||||
createMany: {
|
createMany: {
|
||||||
data: [{ role: WorkspaceRole.ADMIN, userId: 'proUser' }],
|
data: [{ role: WorkspaceRole.ADMIN, userId }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -51,20 +52,20 @@ test.describe('Typebot owner', () => {
|
|||||||
await expect(page.locator('text=Free user')).toBeHidden()
|
await expect(page.locator('text=Free user')).toBeHidden()
|
||||||
await page.fill(
|
await page.fill(
|
||||||
'input[placeholder="colleague@company.com"]',
|
'input[placeholder="colleague@company.com"]',
|
||||||
'free-user@email.com'
|
'other-user@email.com'
|
||||||
)
|
)
|
||||||
await page.click('text=Can edit')
|
await page.click('text=Can edit')
|
||||||
await page.click('text=Can view')
|
await page.click('text=Can view')
|
||||||
await page.click('text=Invite')
|
await page.click('text=Invite')
|
||||||
await expect(page.locator('text=Free user')).toBeVisible()
|
await expect(page.locator('text=James Doe')).toBeVisible()
|
||||||
await page.click('text="guest@email.com"')
|
await page.click('text="guest@email.com"')
|
||||||
await page.click('text="Remove"')
|
await page.click('text="Remove"')
|
||||||
await expect(page.locator('text="guest@email.com"')).toBeHidden()
|
await expect(page.locator('text="guest@email.com"')).toBeHidden()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Collaborator', () => {
|
test.describe('Guest', () => {
|
||||||
test('should display shared typebots', async ({ page }) => {
|
test('should have shared typebots displayed', async ({ page }) => {
|
||||||
const typebotId = cuid()
|
const typebotId = cuid()
|
||||||
const guestWorkspaceId = cuid()
|
const guestWorkspaceId = cuid()
|
||||||
await prisma.workspace.create({
|
await prisma.workspace.create({
|
||||||
@@ -74,7 +75,7 @@ test.describe('Collaborator', () => {
|
|||||||
plan: Plan.FREE,
|
plan: Plan.FREE,
|
||||||
members: {
|
members: {
|
||||||
createMany: {
|
createMany: {
|
||||||
data: [{ role: WorkspaceRole.GUEST, userId: 'proUser' }],
|
data: [{ role: WorkspaceRole.GUEST, userId }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -89,29 +90,34 @@ test.describe('Collaborator', () => {
|
|||||||
options: defaultTextInputOptions,
|
options: defaultTextInputOptions,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Another typebot',
|
||||||
|
workspaceId: guestWorkspaceId,
|
||||||
|
},
|
||||||
])
|
])
|
||||||
await prisma.collaboratorsOnTypebots.create({
|
await prisma.collaboratorsOnTypebots.create({
|
||||||
data: {
|
data: {
|
||||||
typebotId,
|
typebotId,
|
||||||
userId: 'proUser',
|
userId,
|
||||||
type: CollaborationType.READ,
|
type: CollaborationType.READ,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await createFolder(guestWorkspaceId, 'Guest folder')
|
await createFolder(guestWorkspaceId, 'Guest folder')
|
||||||
await createResults({ typebotId, count: 10 })
|
await createResults({ typebotId, count: 10 })
|
||||||
await page.goto(`/typebots`)
|
await page.goto(`/typebots`)
|
||||||
await page.click("text=Pro user's workspace")
|
await page.click('text=Pro workspace')
|
||||||
await page.click('text=Guest workspace #2')
|
await page.click('text=Guest workspace #2')
|
||||||
await expect(page.locator('text=Guest typebot')).toBeVisible()
|
await expect(page.locator('text=Guest typebot')).toBeVisible()
|
||||||
|
await expect(page.locator('text=Another typebot')).toBeHidden()
|
||||||
await expect(page.locator('text=Guest folder')).toBeHidden()
|
await expect(page.locator('text=Guest folder')).toBeHidden()
|
||||||
await page.click('text=Guest typebot')
|
await page.click('text=Guest typebot')
|
||||||
await page.click('button[aria-label="Show collaboration menu"]')
|
await page.click('button[aria-label="Show collaboration menu"]')
|
||||||
await page.click('text=Everyone at Guest workspace')
|
await page.click('text=Everyone at Guest workspace')
|
||||||
await expect(page.locator('text="Remove"')).toBeHidden()
|
await expect(page.locator('text="Remove"')).toBeHidden()
|
||||||
await expect(page.locator('text=Pro user')).toBeVisible()
|
await expect(page.locator('text=John Doe')).toBeVisible()
|
||||||
await page.click('text=Group #1', { force: true })
|
await page.click('text=Group #1', { force: true })
|
||||||
await expect(page.locator('input[value="Group #1"]')).toBeHidden()
|
await expect(page.locator('input[value="Group #1"]')).toBeHidden()
|
||||||
await page.goto(`/typebots/${typebotId}/results`)
|
await page.goto(`/typebots/${typebotId}/results`)
|
||||||
await expect(page.locator('text="See logs" >> nth=10')).toBeVisible()
|
await expect(page.locator('text="See logs" >> nth=9')).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ test.describe('Starter workspace', () => {
|
|||||||
await page.goto(`/typebots/${typebotId}/share`)
|
await page.goto(`/typebots/${typebotId}/share`)
|
||||||
await expect(page.locator('text=Pro')).toBeVisible()
|
await expect(page.locator('text=Pro')).toBeVisible()
|
||||||
await page.click('text=Add my domain')
|
await page.click('text=Add my domain')
|
||||||
await expect(page.locator('text=For solo creator')).toBeVisible()
|
await expect(
|
||||||
|
page.locator(
|
||||||
|
'text="You need to upgrade your plan in order to add custom domains"'
|
||||||
|
)
|
||||||
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,88 +1,87 @@
|
|||||||
import test, { expect, Page } from '@playwright/test'
|
import test, { expect, Page } from '@playwright/test'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import path from 'path'
|
|
||||||
import { createFolders, createTypebots } from '../services/database'
|
import { createFolders, createTypebots } from '../services/database'
|
||||||
import { deleteButtonInConfirmDialog } from '../services/selectorUtils'
|
import { deleteButtonInConfirmDialog } from '../services/selectorUtils'
|
||||||
|
|
||||||
test.describe('Dashboard page', () => {
|
test('folders navigation should work', async ({ page }) => {
|
||||||
test('folders navigation should work', async ({ page }) => {
|
await page.goto('/typebots')
|
||||||
|
const createFolderButton = page.locator('button:has-text("Create a folder")')
|
||||||
|
await expect(createFolderButton).not.toBeDisabled()
|
||||||
|
await createFolderButton.click()
|
||||||
|
await page.click('text="New folder"')
|
||||||
|
await page.fill('input[value="New folder"]', 'My folder #1')
|
||||||
|
await page.press('input[value="My folder #1"]', 'Enter')
|
||||||
|
await waitForNextApiCall(page)
|
||||||
|
await page.click('li:has-text("My folder #1")')
|
||||||
|
await expect(page.locator('h1:has-text("My folder #1")')).toBeVisible()
|
||||||
|
await createFolderButton.click()
|
||||||
|
await page.click('text="New folder"')
|
||||||
|
await page.fill('input', 'My folder #2')
|
||||||
|
await page.press('input', 'Enter')
|
||||||
|
await waitForNextApiCall(page)
|
||||||
|
|
||||||
|
await page.click('li:has-text("My folder #2")')
|
||||||
|
await expect(page.locator('h1 >> text="My folder #2"')).toBeVisible()
|
||||||
|
|
||||||
|
await page.click('text="Back"')
|
||||||
|
await expect(page.locator('span >> text="My folder #2"')).toBeVisible()
|
||||||
|
|
||||||
|
await page.click('text="Back"')
|
||||||
|
await expect(page.locator('span >> text=My folder #1')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('folders and typebots should be deletable', async ({ page }) => {
|
||||||
|
await createFolders([{ name: 'Folder #1' }, { name: 'Folder #2' }])
|
||||||
|
await createTypebots([{ id: 'deletable-typebot', name: 'Typebot #1' }])
|
||||||
|
await page.goto('/typebots')
|
||||||
|
await page.click('button[aria-label="Show Folder #1 menu"]')
|
||||||
|
await page.click('li:has-text("Folder #1") >> button:has-text("Delete")')
|
||||||
|
await deleteButtonInConfirmDialog(page).click()
|
||||||
|
await expect(page.locator('span >> text="Folder #1"')).not.toBeVisible()
|
||||||
|
await page.click('button[aria-label="Show Typebot #1 menu"]')
|
||||||
|
await page.click('li:has-text("Typebot #1") >> button:has-text("Delete")')
|
||||||
|
await deleteButtonInConfirmDialog(page).click()
|
||||||
|
await expect(page.locator('span >> text="Typebot #1"')).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('folders and typebots should be movable', async ({ page }) => {
|
||||||
|
const droppableFolderId = cuid()
|
||||||
|
await createFolders([{ id: droppableFolderId, name: 'Droppable folder' }])
|
||||||
|
await createTypebots([{ name: 'Draggable typebot' }])
|
||||||
|
await page.goto('/typebots')
|
||||||
|
const typebotButton = page.locator('li:has-text("Draggable typebot")')
|
||||||
|
const folderButton = page.locator('li:has-text("Droppable folder")')
|
||||||
|
await page.dragAndDrop(
|
||||||
|
'li:has-text("Draggable typebot")',
|
||||||
|
'li:has-text("Droppable folder")'
|
||||||
|
)
|
||||||
|
await waitForNextApiCall(page)
|
||||||
|
await expect(typebotButton).toBeHidden()
|
||||||
|
await folderButton.click()
|
||||||
|
await expect(page).toHaveURL(new RegExp(`/folders/${droppableFolderId}`))
|
||||||
|
await expect(typebotButton).toBeVisible()
|
||||||
|
await page.dragAndDrop(
|
||||||
|
'li:has-text("Draggable typebot")',
|
||||||
|
'a:has-text("Back")'
|
||||||
|
)
|
||||||
|
await waitForNextApiCall(page)
|
||||||
|
await expect(typebotButton).toBeHidden()
|
||||||
|
await page.click('a:has-text("Back")')
|
||||||
|
await expect(typebotButton).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Free user', () => {
|
||||||
|
test("create folder shouldn't be available", async ({ page }) => {
|
||||||
await page.goto('/typebots')
|
await page.goto('/typebots')
|
||||||
const createFolderButton = page.locator(
|
await page.click('text="Pro workspace"')
|
||||||
'button:has-text("Create a folder")'
|
await page.click('text="Free workspace"')
|
||||||
)
|
await expect(page.locator('[data-testid="starter-plan-tag"]')).toBeVisible()
|
||||||
await expect(createFolderButton).not.toBeDisabled()
|
await page.click('text=Create a folder')
|
||||||
await createFolderButton.click()
|
await expect(
|
||||||
await page.click('text="New folder"')
|
page.locator(
|
||||||
await page.fill('input[value="New folder"]', 'My folder #1')
|
'text="You need to upgrade your plan in order to create folders"'
|
||||||
await page.press('input[value="My folder #1"]', 'Enter')
|
)
|
||||||
await waitForNextApiCall(page)
|
).toBeVisible()
|
||||||
await page.click('li:has-text("My folder #1")')
|
|
||||||
await expect(page.locator('h1:has-text("My folder #1")')).toBeVisible()
|
|
||||||
await createFolderButton.click()
|
|
||||||
await page.click('text="New folder"')
|
|
||||||
await page.fill('input', 'My folder #2')
|
|
||||||
await page.press('input', 'Enter')
|
|
||||||
await waitForNextApiCall(page)
|
|
||||||
|
|
||||||
await page.click('li:has-text("My folder #2")')
|
|
||||||
await expect(page.locator('h1 >> text="My folder #2"')).toBeVisible()
|
|
||||||
|
|
||||||
await page.click('text="Back"')
|
|
||||||
await expect(page.locator('span >> text="My folder #2"')).toBeVisible()
|
|
||||||
|
|
||||||
await page.click('text="Back"')
|
|
||||||
await expect(page.locator('span >> text=My folder #1')).toBeVisible()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('folders and typebots should be deletable', async ({ page }) => {
|
|
||||||
await createFolders([{ name: 'Folder #1' }, { name: 'Folder #2' }])
|
|
||||||
await createTypebots([{ id: 'deletable-typebot', name: 'Typebot #1' }])
|
|
||||||
await page.goto('/typebots')
|
|
||||||
await page.click('button[aria-label="Show Folder #1 menu"]')
|
|
||||||
await page.click('li:has-text("Folder #1") >> button:has-text("Delete")')
|
|
||||||
await deleteButtonInConfirmDialog(page).click()
|
|
||||||
await expect(page.locator('span >> text="Folder #1"')).not.toBeVisible()
|
|
||||||
await page.click('button[aria-label="Show Typebot #1 menu"]')
|
|
||||||
await page.click('li:has-text("Typebot #1") >> button:has-text("Delete")')
|
|
||||||
await deleteButtonInConfirmDialog(page).click()
|
|
||||||
await expect(page.locator('span >> text="Typebot #1"')).not.toBeVisible()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('folders and typebots should be movable', async ({ page }) => {
|
|
||||||
const droppableFolderId = cuid()
|
|
||||||
await createFolders([{ id: droppableFolderId, name: 'Droppable folder' }])
|
|
||||||
await createTypebots([{ name: 'Draggable typebot' }])
|
|
||||||
await page.goto('/typebots')
|
|
||||||
const typebotButton = page.locator('li:has-text("Draggable typebot")')
|
|
||||||
const folderButton = page.locator('li:has-text("Droppable folder")')
|
|
||||||
await page.dragAndDrop(
|
|
||||||
'li:has-text("Draggable typebot")',
|
|
||||||
'li:has-text("Droppable folder")'
|
|
||||||
)
|
|
||||||
await waitForNextApiCall(page)
|
|
||||||
await expect(typebotButton).toBeHidden()
|
|
||||||
await folderButton.click()
|
|
||||||
await expect(page).toHaveURL(new RegExp(`/folders/${droppableFolderId}`))
|
|
||||||
await expect(typebotButton).toBeVisible()
|
|
||||||
await page.dragAndDrop(
|
|
||||||
'li:has-text("Draggable typebot")',
|
|
||||||
'a:has-text("Back")'
|
|
||||||
)
|
|
||||||
await waitForNextApiCall(page)
|
|
||||||
await expect(typebotButton).toBeHidden()
|
|
||||||
await page.click('a:has-text("Back")')
|
|
||||||
await expect(typebotButton).toBeVisible()
|
|
||||||
})
|
|
||||||
|
|
||||||
test.describe('Free user', () => {
|
|
||||||
test.use({
|
|
||||||
storageState: path.join(__dirname, '../secondUser.json'),
|
|
||||||
})
|
|
||||||
test("create folder shouldn't be available", async ({ page }) => {
|
|
||||||
await page.goto('/typebots')
|
|
||||||
await page.click('text=Create a folder')
|
|
||||||
await expect(page.locator('text=For solo creator')).toBeVisible()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -9,174 +9,174 @@ import path from 'path'
|
|||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import { typebotViewer } from '../services/selectorUtils'
|
import { typebotViewer } from '../services/selectorUtils'
|
||||||
|
|
||||||
test.describe.parallel('Editor', () => {
|
test.describe.configure({ mode: 'parallel' })
|
||||||
test('Edges connection should work', async ({ page }) => {
|
|
||||||
const typebotId = cuid()
|
|
||||||
await createTypebots([
|
|
||||||
{
|
|
||||||
id: typebotId,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
|
||||||
await expect(page.locator("text='Start'")).toBeVisible()
|
|
||||||
await page.dragAndDrop('text=Button', '#editor-container', {
|
|
||||||
targetPosition: { x: 1000, y: 400 },
|
|
||||||
})
|
|
||||||
await page.dragAndDrop(
|
|
||||||
'text=Text >> nth=0',
|
|
||||||
'[data-testid="group"] >> nth=1',
|
|
||||||
{
|
|
||||||
targetPosition: { x: 100, y: 50 },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
await page.dragAndDrop(
|
|
||||||
'[data-testid="endpoint"]',
|
|
||||||
'[data-testid="group"] >> nth=1',
|
|
||||||
{ targetPosition: { x: 100, y: 10 } }
|
|
||||||
)
|
|
||||||
await expect(page.locator('[data-testid="edge"]')).toBeVisible()
|
|
||||||
await page.dragAndDrop(
|
|
||||||
'[data-testid="endpoint"]',
|
|
||||||
'[data-testid="group"] >> nth=1'
|
|
||||||
)
|
|
||||||
await expect(page.locator('[data-testid="edge"]')).toBeVisible()
|
|
||||||
await page.dragAndDrop('text=Date', '#editor-container', {
|
|
||||||
targetPosition: { x: 1000, y: 800 },
|
|
||||||
})
|
|
||||||
await page.dragAndDrop(
|
|
||||||
'[data-testid="endpoint"] >> nth=2',
|
|
||||||
'[data-testid="group"] >> nth=2',
|
|
||||||
{
|
|
||||||
targetPosition: { x: 100, y: 10 },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
await expect(page.locator('[data-testid="edge"] >> nth=0')).toBeVisible()
|
|
||||||
await expect(page.locator('[data-testid="edge"] >> nth=1')).toBeVisible()
|
|
||||||
|
|
||||||
await page.click('[data-testid="clickable-edge"] >> nth=0', {
|
test('Edges connection should work', async ({ page }) => {
|
||||||
force: true,
|
const typebotId = cuid()
|
||||||
button: 'right',
|
await createTypebots([
|
||||||
})
|
{
|
||||||
await page.click('text=Delete')
|
id: typebotId,
|
||||||
const total = await page.locator('[data-testid="edge"]').count()
|
},
|
||||||
expect(total).toBe(1)
|
])
|
||||||
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
|
await expect(page.locator("text='Start'")).toBeVisible()
|
||||||
|
await page.dragAndDrop('text=Button', '#editor-container', {
|
||||||
|
targetPosition: { x: 1000, y: 400 },
|
||||||
})
|
})
|
||||||
test('Drag and drop blocks and items should work', async ({ page }) => {
|
await page.dragAndDrop(
|
||||||
const typebotId = cuid()
|
'text=Text >> nth=0',
|
||||||
await importTypebotInDatabase(
|
'[data-testid="group"] >> nth=1',
|
||||||
path.join(__dirname, '../fixtures/typebots/editor/buttonsDnd.json'),
|
{
|
||||||
{
|
targetPosition: { x: 100, y: 50 },
|
||||||
id: typebotId,
|
}
|
||||||
}
|
)
|
||||||
)
|
await page.dragAndDrop(
|
||||||
|
'[data-testid="endpoint"]',
|
||||||
// Blocks dnd
|
'[data-testid="group"] >> nth=1',
|
||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
{ targetPosition: { x: 100, y: 10 } }
|
||||||
await expect(page.locator('[data-testid="block"] >> nth=1')).toHaveText(
|
)
|
||||||
'Hello!'
|
await expect(page.locator('[data-testid="edge"]')).toBeVisible()
|
||||||
)
|
await page.dragAndDrop(
|
||||||
await page.dragAndDrop('text=Hello', '[data-testid="block"] >> nth=3', {
|
'[data-testid="endpoint"]',
|
||||||
targetPosition: { x: 100, y: 0 },
|
'[data-testid="group"] >> nth=1'
|
||||||
})
|
)
|
||||||
await expect(page.locator('[data-testid="block"] >> nth=2')).toHaveText(
|
await expect(page.locator('[data-testid="edge"]')).toBeVisible()
|
||||||
'Hello!'
|
await page.dragAndDrop('text=Date', '#editor-container', {
|
||||||
)
|
targetPosition: { x: 1000, y: 800 },
|
||||||
await page.dragAndDrop('text=Hello', 'text=Group #2')
|
|
||||||
await expect(page.locator('[data-testid="block"] >> nth=3')).toHaveText(
|
|
||||||
'Hello!'
|
|
||||||
)
|
|
||||||
|
|
||||||
// Items dnd
|
|
||||||
await expect(page.locator('[data-testid="item"] >> nth=0')).toHaveText(
|
|
||||||
'Item 1'
|
|
||||||
)
|
|
||||||
await page.dragAndDrop('text=Item 1', 'text=Item 3')
|
|
||||||
await expect(page.locator('[data-testid="item"] >> nth=2')).toHaveText(
|
|
||||||
'Item 1'
|
|
||||||
)
|
|
||||||
await expect(page.locator('[data-testid="item"] >> nth=1')).toHaveText(
|
|
||||||
'Item 3'
|
|
||||||
)
|
|
||||||
await page.dragAndDrop('text=Item 3', 'text=Item 2-3')
|
|
||||||
await expect(page.locator('[data-testid="item"] >> nth=6')).toHaveText(
|
|
||||||
'Item 3'
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
test('Undo / Redo buttons should work', async ({ page }) => {
|
await page.dragAndDrop(
|
||||||
const typebotId = cuid()
|
'[data-testid="endpoint"] >> nth=2',
|
||||||
await createTypebots([
|
'[data-testid="group"] >> nth=2',
|
||||||
{
|
{
|
||||||
id: typebotId,
|
targetPosition: { x: 100, y: 10 },
|
||||||
...parseDefaultGroupWithBlock({
|
}
|
||||||
type: InputBlockType.TEXT,
|
)
|
||||||
options: defaultTextInputOptions,
|
await expect(page.locator('[data-testid="edge"] >> nth=0')).toBeVisible()
|
||||||
}),
|
await expect(page.locator('[data-testid="edge"] >> nth=1')).toBeVisible()
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.click('[data-testid="clickable-edge"] >> nth=0', {
|
||||||
await page.click('text=Group #1', { button: 'right' })
|
force: true,
|
||||||
await page.click('text=Duplicate')
|
button: 'right',
|
||||||
await expect(page.locator('text="Group #1"')).toBeVisible()
|
|
||||||
await expect(page.locator('text="Group #1 copy"')).toBeVisible()
|
|
||||||
await page.click('text="Group #1"', { button: 'right' })
|
|
||||||
await page.click('text=Delete')
|
|
||||||
await expect(page.locator('text="Group #1"')).toBeHidden()
|
|
||||||
await page.click('button[aria-label="Undo"]')
|
|
||||||
await expect(page.locator('text="Group #1"')).toBeVisible()
|
|
||||||
await page.click('button[aria-label="Redo"]')
|
|
||||||
await expect(page.locator('text="Group #1"')).toBeHidden()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Rename and icon change should work', async ({ page }) => {
|
|
||||||
const typebotId = cuid()
|
|
||||||
await createTypebots([
|
|
||||||
{
|
|
||||||
id: typebotId,
|
|
||||||
name: 'My awesome typebot',
|
|
||||||
...parseDefaultGroupWithBlock({
|
|
||||||
type: InputBlockType.TEXT,
|
|
||||||
options: defaultTextInputOptions,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
|
||||||
|
|
||||||
await page.click('[data-testid="editable-icon"]')
|
|
||||||
await expect(page.locator('text="My awesome typebot"')).toBeVisible()
|
|
||||||
await page.fill('input[placeholder="Search..."]', 'love')
|
|
||||||
await page.click('text="😍"')
|
|
||||||
await page.click('text="My awesome typebot"')
|
|
||||||
await page.fill('input[value="My awesome typebot"]', 'My superb typebot')
|
|
||||||
await page.press('input[value="My superb typebot"]', 'Enter')
|
|
||||||
await page.click('[aria-label="Navigate back"]')
|
|
||||||
await expect(page.locator('text="😍"')).toBeVisible()
|
|
||||||
await expect(page.locator('text="My superb typebot"')).toBeVisible()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Preview from group should work', async ({ page }) => {
|
|
||||||
const typebotId = cuid()
|
|
||||||
await importTypebotInDatabase(
|
|
||||||
path.join(__dirname, '../fixtures/typebots/editor/previewFromGroup.json'),
|
|
||||||
{
|
|
||||||
id: typebotId,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
|
||||||
await page.click('[aria-label="Preview bot from this group"] >> nth=1')
|
|
||||||
await expect(
|
|
||||||
typebotViewer(page).locator('text="Hello this is group 1"')
|
|
||||||
).toBeVisible()
|
|
||||||
await page.click('[aria-label="Preview bot from this group"] >> nth=2')
|
|
||||||
await expect(
|
|
||||||
typebotViewer(page).locator('text="Hello this is group 2"')
|
|
||||||
).toBeVisible()
|
|
||||||
await page.click('[aria-label="Close"]')
|
|
||||||
await page.click('text="Preview"')
|
|
||||||
await expect(
|
|
||||||
typebotViewer(page).locator('text="Hello this is group 1"')
|
|
||||||
).toBeVisible()
|
|
||||||
})
|
})
|
||||||
|
await page.click('text=Delete')
|
||||||
|
const total = await page.locator('[data-testid="edge"]').count()
|
||||||
|
expect(total).toBe(1)
|
||||||
|
})
|
||||||
|
test('Drag and drop blocks and items should work', async ({ page }) => {
|
||||||
|
const typebotId = cuid()
|
||||||
|
await importTypebotInDatabase(
|
||||||
|
path.join(__dirname, '../fixtures/typebots/editor/buttonsDnd.json'),
|
||||||
|
{
|
||||||
|
id: typebotId,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Blocks dnd
|
||||||
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
|
await expect(page.locator('[data-testid="block"] >> nth=1')).toHaveText(
|
||||||
|
'Hello!'
|
||||||
|
)
|
||||||
|
await page.dragAndDrop('text=Hello', '[data-testid="block"] >> nth=3', {
|
||||||
|
targetPosition: { x: 100, y: 0 },
|
||||||
|
})
|
||||||
|
await expect(page.locator('[data-testid="block"] >> nth=2')).toHaveText(
|
||||||
|
'Hello!'
|
||||||
|
)
|
||||||
|
await page.dragAndDrop('text=Hello', 'text=Group #2')
|
||||||
|
await expect(page.locator('[data-testid="block"] >> nth=3')).toHaveText(
|
||||||
|
'Hello!'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Items dnd
|
||||||
|
await expect(page.locator('[data-testid="item"] >> nth=0')).toHaveText(
|
||||||
|
'Item 1'
|
||||||
|
)
|
||||||
|
await page.dragAndDrop('text=Item 1', 'text=Item 3')
|
||||||
|
await expect(page.locator('[data-testid="item"] >> nth=2')).toHaveText(
|
||||||
|
'Item 1'
|
||||||
|
)
|
||||||
|
await expect(page.locator('[data-testid="item"] >> nth=1')).toHaveText(
|
||||||
|
'Item 3'
|
||||||
|
)
|
||||||
|
await page.dragAndDrop('text=Item 3', 'text=Item 2-3')
|
||||||
|
await expect(page.locator('[data-testid="item"] >> nth=6')).toHaveText(
|
||||||
|
'Item 3'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
test('Undo / Redo buttons should work', async ({ page }) => {
|
||||||
|
const typebotId = cuid()
|
||||||
|
await createTypebots([
|
||||||
|
{
|
||||||
|
id: typebotId,
|
||||||
|
...parseDefaultGroupWithBlock({
|
||||||
|
type: InputBlockType.TEXT,
|
||||||
|
options: defaultTextInputOptions,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
|
await page.click('text=Group #1', { button: 'right' })
|
||||||
|
await page.click('text=Duplicate')
|
||||||
|
await expect(page.locator('text="Group #1"')).toBeVisible()
|
||||||
|
await expect(page.locator('text="Group #1 copy"')).toBeVisible()
|
||||||
|
await page.click('text="Group #1"', { button: 'right' })
|
||||||
|
await page.click('text=Delete')
|
||||||
|
await expect(page.locator('text="Group #1"')).toBeHidden()
|
||||||
|
await page.click('button[aria-label="Undo"]')
|
||||||
|
await expect(page.locator('text="Group #1"')).toBeVisible()
|
||||||
|
await page.click('button[aria-label="Redo"]')
|
||||||
|
await expect(page.locator('text="Group #1"')).toBeHidden()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Rename and icon change should work', async ({ page }) => {
|
||||||
|
const typebotId = cuid()
|
||||||
|
await createTypebots([
|
||||||
|
{
|
||||||
|
id: typebotId,
|
||||||
|
name: 'My awesome typebot',
|
||||||
|
...parseDefaultGroupWithBlock({
|
||||||
|
type: InputBlockType.TEXT,
|
||||||
|
options: defaultTextInputOptions,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
|
|
||||||
|
await page.click('[data-testid="editable-icon"]')
|
||||||
|
await expect(page.locator('text="My awesome typebot"')).toBeVisible()
|
||||||
|
await page.fill('input[placeholder="Search..."]', 'love')
|
||||||
|
await page.click('text="😍"')
|
||||||
|
await page.click('text="My awesome typebot"')
|
||||||
|
await page.fill('input[value="My awesome typebot"]', 'My superb typebot')
|
||||||
|
await page.press('input[value="My superb typebot"]', 'Enter')
|
||||||
|
await page.click('[aria-label="Navigate back"]')
|
||||||
|
await expect(page.locator('text="😍"')).toBeVisible()
|
||||||
|
await expect(page.locator('text="My superb typebot"')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Preview from group should work', async ({ page }) => {
|
||||||
|
const typebotId = cuid()
|
||||||
|
await importTypebotInDatabase(
|
||||||
|
path.join(__dirname, '../fixtures/typebots/editor/previewFromGroup.json'),
|
||||||
|
{
|
||||||
|
id: typebotId,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
|
await page.click('[aria-label="Preview bot from this group"] >> nth=1')
|
||||||
|
await expect(
|
||||||
|
typebotViewer(page).locator('text="Hello this is group 1"')
|
||||||
|
).toBeVisible()
|
||||||
|
await page.click('[aria-label="Preview bot from this group"] >> nth=2')
|
||||||
|
await expect(
|
||||||
|
typebotViewer(page).locator('text="Hello this is group 2"')
|
||||||
|
).toBeVisible()
|
||||||
|
await page.click('[aria-label="Close"]')
|
||||||
|
await page.click('text="Preview"')
|
||||||
|
await expect(
|
||||||
|
typebotViewer(page).locator('text="Hello this is group 1"')
|
||||||
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ test.describe('Payment input block', () => {
|
|||||||
await stripePaymentForm(page).locator(`[placeholder="CVC"]`).fill('240')
|
await stripePaymentForm(page).locator(`[placeholder="CVC"]`).fill('240')
|
||||||
await typebotViewer(page).locator(`text="Pay 30€"`).click()
|
await typebotViewer(page).locator(`text="Pay 30€"`).click()
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator(`text="Your card was declined."`)
|
typebotViewer(page).locator(`text="Your card has been declined."`)
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await stripePaymentForm(page)
|
await stripePaymentForm(page)
|
||||||
.locator(`[placeholder="1234 1234 1234 1234"]`)
|
.locator(`[placeholder="1234 1234 1234 1234"]`)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ test('results should be deletable', async ({ page }) => {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
await createResults({ typebotId, count: 200 })
|
await createResults({ typebotId, count: 200, isChronological: true })
|
||||||
await page.goto(`/typebots/${typebotId}/results`)
|
await page.goto(`/typebots/${typebotId}/results`)
|
||||||
await selectFirstResults(page)
|
await selectFirstResults(page)
|
||||||
await page.click('text="Delete"')
|
await page.click('text="Delete"')
|
||||||
@@ -67,7 +67,7 @@ test('submissions table should have infinite scroll', async ({ page }) => {
|
|||||||
tableWrapper.scrollTo(0, tableWrapper.scrollHeight)
|
tableWrapper.scrollTo(0, tableWrapper.scrollHeight)
|
||||||
})
|
})
|
||||||
|
|
||||||
await createResults({ typebotId, count: 200 })
|
await createResults({ typebotId, count: 200, isChronological: true })
|
||||||
await page.goto(`/typebots/${typebotId}/results`)
|
await page.goto(`/typebots/${typebotId}/results`)
|
||||||
await expect(page.locator('text=content199')).toBeVisible()
|
await expect(page.locator('text=content199')).toBeVisible()
|
||||||
|
|
||||||
|
|||||||
@@ -135,9 +135,13 @@ test.describe.parallel('Settings page', () => {
|
|||||||
typebotViewer(page).locator('text="What\'s your name?"')
|
typebotViewer(page).locator('text="What\'s your name?"')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await page.click('button:has-text("General")')
|
await page.click('button:has-text("General")')
|
||||||
await expect(page.locator('text=Pro')).toBeVisible()
|
await expect(page.locator('text=Starter')).toBeVisible()
|
||||||
await page.click('text=Typebot.io branding')
|
await page.click('text=Typebot.io branding')
|
||||||
await expect(page.locator('text=For solo creator')).toBeVisible()
|
await expect(
|
||||||
|
page.locator(
|
||||||
|
'text="You need to upgrade your plan in order to remove branding"'
|
||||||
|
)
|
||||||
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import { defaultTextInputOptions, InputBlockType } from 'models'
|
import { defaultTextInputOptions, InputBlockType } from 'models'
|
||||||
import { connectedAsOtherUser } from 'playwright/services/browser'
|
import { mockSessionResponsesToOtherUser } from 'playwright/services/browser'
|
||||||
import {
|
import {
|
||||||
createTypebots,
|
createTypebots,
|
||||||
parseDefaultGroupWithBlock,
|
parseDefaultGroupWithBlock,
|
||||||
@@ -129,7 +129,7 @@ test('can manage members', async ({ page }) => {
|
|||||||
await page.click('button >> text="Remove"')
|
await page.click('button >> text="Remove"')
|
||||||
await expect(page.locator('text="guest@email.com"')).toBeHidden()
|
await expect(page.locator('text="guest@email.com"')).toBeHidden()
|
||||||
|
|
||||||
await connectedAsOtherUser(page)
|
await mockSessionResponsesToOtherUser(page)
|
||||||
await page.goto('/typebots')
|
await page.goto('/typebots')
|
||||||
await page.click('text=Settings & Members')
|
await page.click('text=Settings & Members')
|
||||||
await expect(page.locator('text="Settings"')).toBeHidden()
|
await expect(page.locator('text="Settings"')).toBeHidden()
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export const useMembers = ({ workspaceId }: { workspaceId?: string }) => {
|
|||||||
dedupingInterval: env('E2E_TEST') === 'true' ? 0 : undefined,
|
dedupingInterval: env('E2E_TEST') === 'true' ? 0 : undefined,
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
members: data?.members,
|
members: data?.members ?? [],
|
||||||
invitations: data?.invitations,
|
invitations: data?.invitations ?? [],
|
||||||
isLoading: !error && !data,
|
isLoading: !error && !data,
|
||||||
mutate,
|
mutate,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { WorkspaceWithMembers } from 'contexts/WorkspaceContext'
|
import { WorkspaceWithMembers } from 'contexts/WorkspaceContext'
|
||||||
import { Plan, Workspace } from 'db'
|
import { Plan, Workspace } from 'db'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { isNotDefined, sendRequest } from 'utils'
|
import { isDefined, isNotDefined, sendRequest } from 'utils'
|
||||||
import { fetcher } from '../utils'
|
import { fetcher } from '../utils'
|
||||||
|
|
||||||
export const useWorkspaces = ({ userId }: { userId?: string }) => {
|
export const useWorkspaces = ({ userId }: { userId?: string }) => {
|
||||||
@@ -74,3 +74,6 @@ export const planToReadable = (plan?: Plan) => {
|
|||||||
|
|
||||||
export const isFreePlan = (workspace?: Pick<Workspace, 'plan'>) =>
|
export const isFreePlan = (workspace?: Pick<Workspace, 'plan'>) =>
|
||||||
isNotDefined(workspace) || workspace?.plan === Plan.FREE
|
isNotDefined(workspace) || workspace?.plan === Plan.FREE
|
||||||
|
|
||||||
|
export const isWorkspaceProPlan = (workspace?: Pick<Workspace, 'plan'>) =>
|
||||||
|
isDefined(workspace) && workspace.plan === Plan.PRO
|
||||||
|
|||||||
@@ -17,16 +17,16 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "2.1.0",
|
"@docusaurus/core": "2.1.0",
|
||||||
"@docusaurus/preset-classic": "2.1.0",
|
"@docusaurus/preset-classic": "2.1.0",
|
||||||
"@docusaurus/theme-search-algolia": "^2.1.0",
|
"@docusaurus/theme-search-algolia": "2.1.0",
|
||||||
"@docusaurus/theme-common": "2.1.0",
|
"@docusaurus/theme-common": "2.1.0",
|
||||||
"react": "^17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"@mdx-js/react": "^1.6.22",
|
"@mdx-js/react": "1.6.22",
|
||||||
"@svgr/webpack": "^6.3.1",
|
"@svgr/webpack": "6.3.1",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "1.2.1",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "6.2.0",
|
||||||
"prism-react-renderer": "^1.3.5",
|
"prism-react-renderer": "1.3.5",
|
||||||
"url-loader": "^4.1.1"
|
"url-loader": "4.1.1"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
@@ -41,9 +41,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@algolia/client-search": "^4.14.2",
|
"@algolia/client-search": "4.14.2",
|
||||||
"@types/react": "^18.0.19",
|
"@types/react": "18.0.19",
|
||||||
"typescript": "^4.8.3",
|
"typescript": "4.8.3",
|
||||||
"webpack": "^5.74.0"
|
"webpack": "5.74.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,14 +23,12 @@ interface PricingCardProps extends CardProps {
|
|||||||
data: PricingCardData
|
data: PricingCardData
|
||||||
icon?: JSX.Element
|
icon?: JSX.Element
|
||||||
button: React.ReactElement
|
button: React.ReactElement
|
||||||
isMostPopular?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PricingCard = ({
|
export const PricingCard = ({
|
||||||
data,
|
data,
|
||||||
icon,
|
icon,
|
||||||
button,
|
button,
|
||||||
isMostPopular,
|
|
||||||
...rest
|
...rest
|
||||||
}: PricingCardProps) => {
|
}: PricingCardProps) => {
|
||||||
const { features, price, name } = data
|
const { features, price, name } = data
|
||||||
|
|||||||
@@ -219,7 +219,6 @@ const Pricing = () => {
|
|||||||
<ActionButton>Subscribe now</ActionButton>
|
<ActionButton>Subscribe now</ActionButton>
|
||||||
</NextChakraLink>
|
</NextChakraLink>
|
||||||
}
|
}
|
||||||
isMostPopular
|
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<VStack maxW="1200px" w="full" spacing={[12, 20]} px="4">
|
<VStack maxW="1200px" w="full" spacing={[12, 20]} px="4">
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ const checkStorageLimit = async (typebotId: string) => {
|
|||||||
})
|
})
|
||||||
if (!typebot?.workspace) throw new Error('Workspace not found')
|
if (!typebot?.workspace) throw new Error('Workspace not found')
|
||||||
const { workspace } = typebot
|
const { workspace } = typebot
|
||||||
|
console.log(typebot.workspaceId)
|
||||||
const {
|
const {
|
||||||
_sum: { storageUsed: totalStorageUsed },
|
_sum: { storageUsed: totalStorageUsed },
|
||||||
} = await prisma.answer.aggregate({
|
} = await prisma.answer.aggregate({
|
||||||
@@ -94,26 +95,27 @@ const checkStorageLimit = async (typebotId: string) => {
|
|||||||
if (!totalStorageUsed) return false
|
if (!totalStorageUsed) return false
|
||||||
const hasSentFirstEmail = workspace.storageLimitFirstEmailSentAt !== null
|
const hasSentFirstEmail = workspace.storageLimitFirstEmailSentAt !== null
|
||||||
const hasSentSecondEmail = workspace.storageLimitSecondEmailSentAt !== null
|
const hasSentSecondEmail = workspace.storageLimitSecondEmailSentAt !== null
|
||||||
const storageLimit = getStorageLimit(typebot.workspace) * 1024 * 1024 * 1024
|
const storageLimit = getStorageLimit(typebot.workspace)
|
||||||
|
const storageLimitBytes = storageLimit * 1024 * 1024 * 1024
|
||||||
if (
|
if (
|
||||||
totalStorageUsed >= storageLimit * LIMIT_EMAIL_TRIGGER_PERCENT &&
|
totalStorageUsed >= storageLimitBytes * LIMIT_EMAIL_TRIGGER_PERCENT &&
|
||||||
!hasSentFirstEmail &&
|
!hasSentFirstEmail &&
|
||||||
env('E2E_TEST') !== 'true'
|
env('E2E_TEST') !== 'true'
|
||||||
)
|
)
|
||||||
sendAlmostReachStorageLimitEmail({
|
await sendAlmostReachStorageLimitEmail({
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
storageLimit,
|
storageLimit,
|
||||||
})
|
})
|
||||||
if (
|
if (
|
||||||
totalStorageUsed >= storageLimit &&
|
totalStorageUsed >= storageLimitBytes &&
|
||||||
!hasSentSecondEmail &&
|
!hasSentSecondEmail &&
|
||||||
env('E2E_TEST') !== 'true'
|
env('E2E_TEST') !== 'true'
|
||||||
)
|
)
|
||||||
sendReachStorageLimitEmail({
|
await sendReachStorageLimitEmail({
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
storageLimit,
|
storageLimit,
|
||||||
})
|
})
|
||||||
return (totalStorageUsed ?? 0) >= getStorageLimit(typebot?.workspace)
|
return totalStorageUsed >= storageLimitBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendAlmostReachStorageLimitEmail = async ({
|
const sendAlmostReachStorageLimitEmail = async ({
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ const checkChatsUsage = async (
|
|||||||
| 'chatsLimitSecondEmailSentAt'
|
| 'chatsLimitSecondEmailSentAt'
|
||||||
>
|
>
|
||||||
) => {
|
) => {
|
||||||
const chatLimit = getChatsLimit(workspace)
|
const chatsLimit = getChatsLimit(workspace)
|
||||||
if (chatLimit === -1) return
|
if (chatsLimit === -1) return
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
|
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||||
const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
|
const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
|
||||||
@@ -91,26 +91,26 @@ const checkChatsUsage = async (
|
|||||||
workspace.chatsLimitSecondEmailSentAt < firstDayOfNextMonth &&
|
workspace.chatsLimitSecondEmailSentAt < firstDayOfNextMonth &&
|
||||||
workspace.chatsLimitSecondEmailSentAt > firstDayOfMonth
|
workspace.chatsLimitSecondEmailSentAt > firstDayOfMonth
|
||||||
if (
|
if (
|
||||||
chatsCount >= chatLimit * LIMIT_EMAIL_TRIGGER_PERCENT &&
|
chatsCount >= chatsLimit * LIMIT_EMAIL_TRIGGER_PERCENT &&
|
||||||
!hasSentFirstEmail &&
|
!hasSentFirstEmail &&
|
||||||
env('E2E_TEST') !== 'true'
|
env('E2E_TEST') !== 'true'
|
||||||
)
|
)
|
||||||
await sendAlmostReachChatsLimitEmail({
|
await sendAlmostReachChatsLimitEmail({
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
chatLimit,
|
chatLimit: chatsLimit,
|
||||||
firstDayOfNextMonth,
|
firstDayOfNextMonth,
|
||||||
})
|
})
|
||||||
if (
|
if (
|
||||||
chatsCount >= chatLimit &&
|
chatsCount >= chatsLimit &&
|
||||||
!hasSentSecondEmail &&
|
!hasSentSecondEmail &&
|
||||||
env('E2E_TEST') !== 'true'
|
env('E2E_TEST') !== 'true'
|
||||||
)
|
)
|
||||||
await sendReachedAlertEmail({
|
await sendReachedAlertEmail({
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
chatLimit,
|
chatLimit: chatsLimit,
|
||||||
firstDayOfNextMonth,
|
firstDayOfNextMonth,
|
||||||
})
|
})
|
||||||
return chatsCount >= chatLimit
|
return chatsCount >= chatsLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendAlmostReachChatsLimitEmail = async ({
|
const sendAlmostReachChatsLimitEmail = async ({
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Page } from '@playwright/test'
|
import { Page } from '@playwright/test'
|
||||||
|
|
||||||
export const mockSessionApiCalls = (page: Page) =>
|
export const mockSessionResponsesToOtherUser = async (page: Page) =>
|
||||||
page.route(`${process.env.BUILDER_URL}/api/auth/session`, (route) => {
|
page.route('/api/auth/session', (route) => {
|
||||||
if (route.request().method() === 'GET') {
|
if (route.request().method() === 'GET') {
|
||||||
return route.fulfill({
|
return route.fulfill({
|
||||||
status: 200,
|
status: 200,
|
||||||
body: '{"user":{"id":"proUser","name":"Pro user","email":"pro-user@email.com","emailVerified":null,"image":"https://avatars.githubusercontent.com/u/16015833?v=4","stripeId":null,"graphNavigation": "TRACKPAD"}}',
|
body: '{"user":{"id":"otherUserId","name":"James Doe","email":"other-user@email.com","emailVerified":null,"image":"https://avatars.githubusercontent.com/u/16015833?v=4","stripeId":null,"graphNavigation": "TRACKPAD"}}',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return route.continue()
|
return route.continue()
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ const prisma = new PrismaClient()
|
|||||||
const userId = 'userId'
|
const userId = 'userId'
|
||||||
export const freeWorkspaceId = 'freeWorkspace'
|
export const freeWorkspaceId = 'freeWorkspace'
|
||||||
export const starterWorkspaceId = 'starterWorkspace'
|
export const starterWorkspaceId = 'starterWorkspace'
|
||||||
|
export const limitTestWorkspaceId = 'limitTestWorkspace'
|
||||||
|
export const apiToken = 'jirowjgrwGREHE'
|
||||||
|
|
||||||
export const teardownDatabase = async () => {
|
export const teardownDatabase = async () => {
|
||||||
await prisma.workspace.deleteMany({
|
await prisma.workspace.deleteMany({
|
||||||
@@ -51,6 +53,11 @@ export const createWorkspaces = async () =>
|
|||||||
name: 'Starter workspace',
|
name: 'Starter workspace',
|
||||||
plan: Plan.STARTER,
|
plan: Plan.STARTER,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: limitTestWorkspaceId,
|
||||||
|
name: 'Limit test workspace',
|
||||||
|
plan: Plan.FREE,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -65,20 +72,10 @@ export const createUser = async () => {
|
|||||||
createMany: {
|
createMany: {
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
name: 'Token 1',
|
name: 'Token',
|
||||||
token: 'jirowjgrwGREHEtoken1',
|
token: apiToken,
|
||||||
createdAt: new Date(2022, 1, 1),
|
createdAt: new Date(2022, 1, 1),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'Github',
|
|
||||||
token: 'jirowjgrwGREHEgdrgithub',
|
|
||||||
createdAt: new Date(2022, 1, 2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'N8n',
|
|
||||||
token: 'jirowjgrwGREHrgwhrwn8n',
|
|
||||||
createdAt: new Date(2022, 1, 3),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -88,6 +85,7 @@ export const createUser = async () => {
|
|||||||
data: [
|
data: [
|
||||||
{ role: WorkspaceRole.ADMIN, userId, workspaceId: freeWorkspaceId },
|
{ role: WorkspaceRole.ADMIN, userId, workspaceId: freeWorkspaceId },
|
||||||
{ role: WorkspaceRole.ADMIN, userId, workspaceId: starterWorkspaceId },
|
{ role: WorkspaceRole.ADMIN, userId, workspaceId: starterWorkspaceId },
|
||||||
|
{ role: WorkspaceRole.ADMIN, userId, workspaceId: limitTestWorkspaceId },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -207,8 +205,8 @@ export const importTypebotInDatabase = async (
|
|||||||
) => {
|
) => {
|
||||||
const typebot: Typebot = {
|
const typebot: Typebot = {
|
||||||
...JSON.parse(readFileSync(path).toString()),
|
...JSON.parse(readFileSync(path).toString()),
|
||||||
...updates,
|
|
||||||
workspaceId: starterWorkspaceId,
|
workspaceId: starterWorkspaceId,
|
||||||
|
...updates,
|
||||||
}
|
}
|
||||||
await prisma.typebot.create({
|
await prisma.typebot.create({
|
||||||
data: typebot,
|
data: typebot,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import {
|
import {
|
||||||
|
apiToken,
|
||||||
createResults,
|
createResults,
|
||||||
createWebhook,
|
createWebhook,
|
||||||
importTypebotInDatabase,
|
importTypebotInDatabase,
|
||||||
@@ -23,7 +24,7 @@ test.beforeAll(async () => {
|
|||||||
test('can list typebots', async ({ request }) => {
|
test('can list typebots', async ({ request }) => {
|
||||||
expect((await request.get(`/api/typebots`)).status()).toBe(401)
|
expect((await request.get(`/api/typebots`)).status()).toBe(401)
|
||||||
const response = await request.get(`/api/typebots`, {
|
const response = await request.get(`/api/typebots`, {
|
||||||
headers: { Authorization: 'Bearer userToken' },
|
headers: { Authorization: `Bearer ${apiToken}` },
|
||||||
})
|
})
|
||||||
const { typebots } = await response.json()
|
const { typebots } = await response.json()
|
||||||
expect(typebots).toHaveLength(1)
|
expect(typebots).toHaveLength(1)
|
||||||
@@ -41,7 +42,7 @@ test('can get webhook blocks', async ({ request }) => {
|
|||||||
const response = await request.get(
|
const response = await request.get(
|
||||||
`/api/typebots/${typebotId}/webhookBlocks`,
|
`/api/typebots/${typebotId}/webhookBlocks`,
|
||||||
{
|
{
|
||||||
headers: { Authorization: 'Bearer userToken' },
|
headers: { Authorization: `Bearer ${apiToken}` },
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const { blocks } = await response.json()
|
const { blocks } = await response.json()
|
||||||
@@ -65,7 +66,7 @@ test('can subscribe webhook', async ({ request }) => {
|
|||||||
`/api/typebots/${typebotId}/blocks/webhookBlock/subscribeWebhook`,
|
`/api/typebots/${typebotId}/blocks/webhookBlock/subscribeWebhook`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: 'Bearer userToken',
|
Authorization: `Bearer ${apiToken}`,
|
||||||
},
|
},
|
||||||
data: { url: 'https://test.com' },
|
data: { url: 'https://test.com' },
|
||||||
}
|
}
|
||||||
@@ -87,7 +88,7 @@ test('can unsubscribe webhook', async ({ request }) => {
|
|||||||
const response = await request.post(
|
const response = await request.post(
|
||||||
`/api/typebots/${typebotId}/blocks/webhookBlock/unsubscribeWebhook`,
|
`/api/typebots/${typebotId}/blocks/webhookBlock/unsubscribeWebhook`,
|
||||||
{
|
{
|
||||||
headers: { Authorization: 'Bearer userToken' },
|
headers: { Authorization: `Bearer ${apiToken}` },
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const body = await response.json()
|
const body = await response.json()
|
||||||
@@ -107,7 +108,7 @@ test('can get a sample result', async ({ request }) => {
|
|||||||
const response = await request.get(
|
const response = await request.get(
|
||||||
`/api/typebots/${typebotId}/blocks/webhookBlock/sampleResult`,
|
`/api/typebots/${typebotId}/blocks/webhookBlock/sampleResult`,
|
||||||
{
|
{
|
||||||
headers: { Authorization: 'Bearer userToken' },
|
headers: { Authorization: `Bearer ${apiToken}` },
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
@@ -128,7 +129,7 @@ test('can list results', async ({ request }) => {
|
|||||||
const response = await request.get(
|
const response = await request.get(
|
||||||
`/api/typebots/${typebotId}/results?limit=10`,
|
`/api/typebots/${typebotId}/results?limit=10`,
|
||||||
{
|
{
|
||||||
headers: { Authorization: 'Bearer userToken' },
|
headers: { Authorization: `Bearer ${apiToken}` },
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const { results } = await response.json()
|
const { results } = await response.json()
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { typebotViewer } from '../services/selectorUtils'
|
|||||||
import { createResults, importTypebotInDatabase } from '../services/database'
|
import { createResults, importTypebotInDatabase } from '../services/database'
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
import { isDefined } from 'utils'
|
import { isDefined } from 'utils'
|
||||||
import { describe } from 'node:test'
|
|
||||||
|
|
||||||
const THREE_GIGABYTES = 3 * 1024 * 1024 * 1024
|
const THREE_GIGABYTES = 3 * 1024 * 1024 * 1024
|
||||||
|
|
||||||
@@ -49,7 +48,7 @@ test('should work as expected', async ({ page, browser }) => {
|
|||||||
page.locator('text="Export"').click(),
|
page.locator('text="Export"').click(),
|
||||||
])
|
])
|
||||||
const downloadPath = await download.path()
|
const downloadPath = await download.path()
|
||||||
expect(path).toBeDefined()
|
expect(downloadPath).toBeDefined()
|
||||||
const file = readFileSync(downloadPath as string).toString()
|
const file = readFileSync(downloadPath as string).toString()
|
||||||
const { data } = parse(file)
|
const { data } = parse(file)
|
||||||
expect(data).toHaveLength(2)
|
expect(data).toHaveLength(2)
|
||||||
@@ -86,7 +85,7 @@ test('should work as expected', async ({ page, browser }) => {
|
|||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Storage limit is reached', () => {
|
test.describe('Storage limit is reached', () => {
|
||||||
const typebotId = cuid()
|
const typebotId = cuid()
|
||||||
|
|
||||||
test.beforeAll(async () => {
|
test.beforeAll(async () => {
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ import path from 'path'
|
|||||||
import { importTypebotInDatabase } from '../services/database'
|
import { importTypebotInDatabase } from '../services/database'
|
||||||
import { typebotViewer } from '../services/selectorUtils'
|
import { typebotViewer } from '../services/selectorUtils'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import { mockSessionApiCalls } from 'playwright/services/browser'
|
|
||||||
|
|
||||||
test.beforeEach(({ page }) => mockSessionApiCalls(page))
|
|
||||||
|
|
||||||
test('should work as expected', async ({ page }) => {
|
test('should work as expected', async ({ page }) => {
|
||||||
const typebotId = cuid()
|
const typebotId = cuid()
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import {
|
import {
|
||||||
createResults,
|
createResults,
|
||||||
freeWorkspaceId,
|
|
||||||
importTypebotInDatabase,
|
importTypebotInDatabase,
|
||||||
|
limitTestWorkspaceId,
|
||||||
} from '../services/database'
|
} from '../services/database'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
@@ -14,7 +14,7 @@ test('should not start if chat limit is reached', async ({ page }) => {
|
|||||||
{
|
{
|
||||||
id: typebotId,
|
id: typebotId,
|
||||||
publicId: `${typebotId}-public`,
|
publicId: `${typebotId}-public`,
|
||||||
workspaceId: freeWorkspaceId,
|
workspaceId: limitTestWorkspaceId,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await createResults({ typebotId, count: 320 })
|
await createResults({ typebotId, count: 320 })
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ import {
|
|||||||
} from 'models'
|
} from 'models'
|
||||||
import { typebotViewer } from '../services/selectorUtils'
|
import { typebotViewer } from '../services/selectorUtils'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import { mockSessionApiCalls } from 'playwright/services/browser'
|
|
||||||
|
|
||||||
test.beforeEach(({ page }) => mockSessionApiCalls(page))
|
|
||||||
|
|
||||||
test('Should correctly parse metadata', async ({ page }) => {
|
test('Should correctly parse metadata', async ({ page }) => {
|
||||||
const typebotId = cuid()
|
const typebotId = cuid()
|
||||||
@@ -37,20 +34,20 @@ test('Should correctly parse metadata', async ({ page }) => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
await page.goto(`/${typebotId}-public`)
|
await page.goto(`/${typebotId}-public`)
|
||||||
await expect(
|
expect(
|
||||||
await page.evaluate(`document.querySelector('title').textContent`)
|
await page.evaluate(`document.querySelector('title').textContent`)
|
||||||
).toBe(customMetadata.title)
|
).toBe(customMetadata.title)
|
||||||
await expect(
|
expect(
|
||||||
await page.evaluate(
|
await page.evaluate(
|
||||||
() => (document.querySelector('meta[name="description"]') as any).content
|
() => (document.querySelector('meta[name="description"]') as any).content
|
||||||
)
|
)
|
||||||
).toBe(customMetadata.description)
|
).toBe(customMetadata.description)
|
||||||
await expect(
|
expect(
|
||||||
await page.evaluate(
|
await page.evaluate(
|
||||||
() => (document.querySelector('meta[property="og:image"]') as any).content
|
() => (document.querySelector('meta[property="og:image"]') as any).content
|
||||||
)
|
)
|
||||||
).toBe(customMetadata.imageUrl)
|
).toBe(customMetadata.imageUrl)
|
||||||
await expect(
|
expect(
|
||||||
await page.evaluate(() =>
|
await page.evaluate(() =>
|
||||||
(document.querySelector('link[rel="icon"]') as any).getAttribute('href')
|
(document.querySelector('link[rel="icon"]') as any).getAttribute('href')
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ import { importTypebotInDatabase } from '../services/database'
|
|||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { typebotViewer } from '../services/selectorUtils'
|
import { typebotViewer } from '../services/selectorUtils'
|
||||||
import { mockSessionApiCalls } from 'playwright/services/browser'
|
|
||||||
|
|
||||||
test.beforeEach(({ page }) => mockSessionApiCalls(page))
|
|
||||||
|
|
||||||
test('should correctly be injected', async ({ page }) => {
|
test('should correctly be injected', async ({ page }) => {
|
||||||
const typebotId = cuid()
|
const typebotId = cuid()
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ import cuid from 'cuid'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { typebotViewer } from '../services/selectorUtils'
|
import { typebotViewer } from '../services/selectorUtils'
|
||||||
import { SmtpCredentialsData } from 'models'
|
import { SmtpCredentialsData } from 'models'
|
||||||
import { mockSessionApiCalls } from 'playwright/services/browser'
|
|
||||||
|
|
||||||
test.beforeEach(({ page }) => mockSessionApiCalls(page))
|
|
||||||
|
|
||||||
const mockSmtpCredentials: SmtpCredentialsData = {
|
const mockSmtpCredentials: SmtpCredentialsData = {
|
||||||
from: {
|
from: {
|
||||||
|
|||||||
@@ -10,9 +10,6 @@ import {
|
|||||||
defaultTextInputOptions,
|
defaultTextInputOptions,
|
||||||
InputBlockType,
|
InputBlockType,
|
||||||
} from 'models'
|
} from 'models'
|
||||||
import { mockSessionApiCalls } from 'playwright/services/browser'
|
|
||||||
|
|
||||||
test.beforeEach(({ page }) => mockSessionApiCalls(page))
|
|
||||||
|
|
||||||
test('Result should be in storage by default', async ({ page }) => {
|
test('Result should be in storage by default', async ({ page }) => {
|
||||||
const typebotId = cuid()
|
const typebotId = cuid()
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ import test, { expect } from '@playwright/test'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { importTypebotInDatabase } from '../services/database'
|
import { importTypebotInDatabase } from '../services/database'
|
||||||
import { typebotViewer } from '../services/selectorUtils'
|
import { typebotViewer } from '../services/selectorUtils'
|
||||||
import { mockSessionApiCalls } from 'playwright/services/browser'
|
|
||||||
|
|
||||||
test.beforeEach(({ page }) => mockSessionApiCalls(page))
|
|
||||||
|
|
||||||
test('should work as expected', async ({ page }) => {
|
test('should work as expected', async ({ page }) => {
|
||||||
const typebotId = 'cl0ibhi7s0018n21aarlmg0cm'
|
const typebotId = 'cl0ibhi7s0018n21aarlmg0cm'
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ import cuid from 'cuid'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { typebotViewer } from '../services/selectorUtils'
|
import { typebotViewer } from '../services/selectorUtils'
|
||||||
import { HttpMethod } from 'models'
|
import { HttpMethod } from 'models'
|
||||||
import { mockSessionApiCalls } from 'playwright/services/browser'
|
|
||||||
|
|
||||||
test.beforeEach(({ page }) => mockSessionApiCalls(page))
|
|
||||||
|
|
||||||
test('should execute webhooks properly', async ({ page }) => {
|
test('should execute webhooks properly', async ({ page }) => {
|
||||||
const typebotId = cuid()
|
const typebotId = cuid()
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export const ConversationContainer = ({
|
|||||||
}
|
}
|
||||||
const nextGroup = currentTypebot.groups.find(byId(nextEdge.to.groupId))
|
const nextGroup = currentTypebot.groups.find(byId(nextEdge.to.groupId))
|
||||||
if (!nextGroup) return onCompleted()
|
if (!nextGroup) return onCompleted()
|
||||||
|
console.log(nextGroup, nextEdge)
|
||||||
const startBlockIndex = nextEdge.to.blockId
|
const startBlockIndex = nextEdge.to.blockId
|
||||||
? nextGroup.blocks.findIndex(byId(nextEdge.to.blockId))
|
? nextGroup.blocks.findIndex(byId(nextEdge.to.blockId))
|
||||||
: 0
|
: 0
|
||||||
@@ -151,9 +152,7 @@ export const ConversationContainer = ({
|
|||||||
const groupAfter = displayedGroups[idx + 1]
|
const groupAfter = displayedGroups[idx + 1]
|
||||||
const groupAfterStartsWithInput =
|
const groupAfterStartsWithInput =
|
||||||
groupAfter &&
|
groupAfter &&
|
||||||
isInputBlock(
|
isInputBlock(groupAfter.group.blocks[groupAfter.startBlockIndex])
|
||||||
groupAfter.group.blocks[groupAfter.startBlockIndex] as Block
|
|
||||||
)
|
|
||||||
return (
|
return (
|
||||||
<ChatGroup
|
<ChatGroup
|
||||||
key={displayedGroup.group.id + idx}
|
key={displayedGroup.group.id + idx}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/nodemailer": "6.4.5",
|
"@types/nodemailer": "6.4.5",
|
||||||
"aws-sdk": "2.1213.0",
|
"aws-sdk": "2.1213.0",
|
||||||
|
"cuid": "^2.1.8",
|
||||||
"db": "workspace:*",
|
"db": "workspace:*",
|
||||||
"models": "workspace:*",
|
"models": "workspace:*",
|
||||||
"next": "12.3.0",
|
"next": "12.3.0",
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { PrismaClient } from 'db'
|
import { PrismaClient } from 'db'
|
||||||
|
import cuid from 'cuid'
|
||||||
|
|
||||||
type CreateFakeResultsProps = {
|
type CreateFakeResultsProps = {
|
||||||
typebotId: string
|
typebotId: string
|
||||||
count: number
|
count: number
|
||||||
idPrefix?: string
|
customResultIdPrefix?: string
|
||||||
isChronological?: boolean
|
isChronological?: boolean
|
||||||
fakeStorage?: number
|
fakeStorage?: number
|
||||||
}
|
}
|
||||||
@@ -12,18 +13,19 @@ export const injectFakeResults =
|
|||||||
(prisma: PrismaClient) =>
|
(prisma: PrismaClient) =>
|
||||||
async ({
|
async ({
|
||||||
count,
|
count,
|
||||||
idPrefix = '',
|
customResultIdPrefix,
|
||||||
typebotId,
|
typebotId,
|
||||||
isChronological = true,
|
isChronological,
|
||||||
fakeStorage,
|
fakeStorage,
|
||||||
}: CreateFakeResultsProps) => {
|
}: CreateFakeResultsProps) => {
|
||||||
|
const resultIdPrefix = customResultIdPrefix ?? cuid()
|
||||||
await prisma.result.createMany({
|
await prisma.result.createMany({
|
||||||
data: [
|
data: [
|
||||||
...Array.from(Array(count)).map((_, idx) => {
|
...Array.from(Array(count)).map((_, idx) => {
|
||||||
const today = new Date()
|
const today = new Date()
|
||||||
const rand = Math.random()
|
const rand = Math.random()
|
||||||
return {
|
return {
|
||||||
id: `${idPrefix}-result${idx}`,
|
id: `${resultIdPrefix}-result${idx}`,
|
||||||
typebotId,
|
typebotId,
|
||||||
createdAt: isChronological
|
createdAt: isChronological
|
||||||
? new Date(
|
? new Date(
|
||||||
@@ -36,20 +38,23 @@ export const injectFakeResults =
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
return createAnswers(prisma)({ idPrefix, fakeStorage, count })
|
return createAnswers(prisma)({ fakeStorage, resultIdPrefix, count })
|
||||||
}
|
}
|
||||||
|
|
||||||
const createAnswers =
|
const createAnswers =
|
||||||
(prisma: PrismaClient) =>
|
(prisma: PrismaClient) =>
|
||||||
({
|
({
|
||||||
count,
|
count,
|
||||||
idPrefix,
|
resultIdPrefix,
|
||||||
fakeStorage,
|
fakeStorage,
|
||||||
}: Pick<CreateFakeResultsProps, 'fakeStorage' | 'idPrefix' | 'count'>) => {
|
}: { resultIdPrefix: string } & Pick<
|
||||||
|
CreateFakeResultsProps,
|
||||||
|
'fakeStorage' | 'count'
|
||||||
|
>) => {
|
||||||
return prisma.answer.createMany({
|
return prisma.answer.createMany({
|
||||||
data: [
|
data: [
|
||||||
...Array.from(Array(count)).map((_, idx) => ({
|
...Array.from(Array(count)).map((_, idx) => ({
|
||||||
resultId: `${idPrefix}-result${idx}`,
|
resultId: `${resultIdPrefix}-result${idx}`,
|
||||||
content: `content${idx}`,
|
content: `content${idx}`,
|
||||||
blockId: 'block1',
|
blockId: 'block1',
|
||||||
groupId: 'block1',
|
groupId: 'block1',
|
||||||
|
|||||||
2141
pnpm-lock.yaml
generated
2141
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user