🐛 (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 { Graph } from 'components/shared/Graph'
|
||||
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 { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||
import { Stats } from 'models'
|
||||
@@ -49,7 +52,11 @@ export const AnalyticsContent = ({ stats }: { stats?: Stats }) => {
|
||||
<Spinner color="gray" />
|
||||
</Flex>
|
||||
)}
|
||||
<ChangePlanModal onClose={onClose} isOpen={isOpen} />
|
||||
<ChangePlanModal
|
||||
onClose={onClose}
|
||||
isOpen={isOpen}
|
||||
type={LimitReached.ANALYTICS}
|
||||
/>
|
||||
<StatsCards stats={stats} pos="absolute" top={10} />
|
||||
</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 {
|
||||
LimitReached,
|
||||
ChangePlanModal,
|
||||
} from 'components/shared/modals/ChangePlanModal'
|
||||
import { PlanTag } from 'components/shared/PlanTag'
|
||||
import { useWorkspace } from 'contexts/WorkspaceContext'
|
||||
import { Plan } from 'db'
|
||||
import React from 'react'
|
||||
import { isFreePlan } from 'services/workspace'
|
||||
|
||||
@@ -26,7 +28,7 @@ export const CreateFolderButton = ({ isLoading, onClick }: Props) => {
|
||||
>
|
||||
<HStack>
|
||||
<Text>Create a folder</Text>
|
||||
{isFreePlan(workspace) && <Tag colorScheme="orange">Pro</Tag>}
|
||||
{isFreePlan(workspace) && <PlanTag plan={Plan.STARTER} />}
|
||||
</HStack>
|
||||
<ChangePlanModal
|
||||
isOpen={isOpen}
|
||||
|
||||
@@ -7,7 +7,7 @@ export const useUsage = (workspaceId?: string) => {
|
||||
{ totalChatsUsed: number; totalStorageUsed: number },
|
||||
Error
|
||||
>(workspaceId ? `/api/workspaces/${workspaceId}/usage` : null, fetcher, {
|
||||
dedupingInterval: env('E2E_TEST') === 'enabled' ? 0 : undefined,
|
||||
dedupingInterval: env('E2E_TEST') === 'true' ? 0 : undefined,
|
||||
})
|
||||
return {
|
||||
data,
|
||||
|
||||
@@ -14,7 +14,7 @@ export const useInvoicesQuery = (stripeId?: string | null) => {
|
||||
stripeId ? `/api/stripe/invoices?stripeId=${stripeId}` : null,
|
||||
fetcher,
|
||||
{
|
||||
dedupingInterval: env('E2E_TEST') === 'enabled' ? 0 : undefined,
|
||||
dedupingInterval: env('E2E_TEST') === 'true' ? 0 : undefined,
|
||||
}
|
||||
)
|
||||
return {
|
||||
|
||||
@@ -24,7 +24,7 @@ export const MembersList = () => {
|
||||
})
|
||||
|
||||
const handleDeleteMemberClick = (memberId: string) => async () => {
|
||||
if (!workspace || !members || !invitations) return
|
||||
if (!workspace) return
|
||||
await deleteMember(workspace.id, memberId)
|
||||
mutate({
|
||||
members: members.filter((m) => m.userId !== memberId),
|
||||
@@ -34,7 +34,7 @@ export const MembersList = () => {
|
||||
|
||||
const handleSelectNewRole =
|
||||
(memberId: string) => async (role: WorkspaceRole) => {
|
||||
if (!workspace || !members || !invitations) return
|
||||
if (!workspace) return
|
||||
await updateMember(workspace.id, { userId: memberId, role })
|
||||
mutate({
|
||||
members: members.map((m) =>
|
||||
@@ -45,7 +45,7 @@ export const MembersList = () => {
|
||||
}
|
||||
|
||||
const handleDeleteInvitationClick = (id: string) => async () => {
|
||||
if (!workspace || !members || !invitations) return
|
||||
if (!workspace) return
|
||||
await deleteInvitation({ workspaceId: workspace.id, id })
|
||||
mutate({
|
||||
invitations: invitations.filter((i) => i.id !== id),
|
||||
@@ -55,7 +55,7 @@ export const MembersList = () => {
|
||||
|
||||
const handleSelectNewInvitationRole =
|
||||
(id: string) => async (type: WorkspaceRole) => {
|
||||
if (!workspace || !members || !invitations) return
|
||||
if (!workspace) return
|
||||
await updateInvitation({ workspaceId: workspace.id, id, type })
|
||||
mutate({
|
||||
invitations: invitations.map((i) => (i.id === id ? { ...i, type } : i)),
|
||||
@@ -63,17 +63,15 @@ export const MembersList = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleNewInvitation = (invitation: WorkspaceInvitation) => {
|
||||
if (!members || !invitations) return
|
||||
mutate({
|
||||
const handleNewInvitation = async (invitation: WorkspaceInvitation) => {
|
||||
await mutate({
|
||||
members,
|
||||
invitations: [...invitations, invitation],
|
||||
})
|
||||
}
|
||||
|
||||
const handleNewMember = (member: Member) => {
|
||||
if (!members || !invitations) return
|
||||
mutate({
|
||||
const handleNewMember = async (member: Member) => {
|
||||
await mutate({
|
||||
members: [...members, member],
|
||||
invitations,
|
||||
})
|
||||
@@ -81,7 +79,7 @@ export const MembersList = () => {
|
||||
|
||||
const canInviteNewMember = checkCanInviteMember({
|
||||
plan: workspace?.plan,
|
||||
currentMembersCount: [...(members ?? []), ...(invitations ?? [])].length,
|
||||
currentMembersCount: [...members, ...invitations].length,
|
||||
})
|
||||
|
||||
return (
|
||||
@@ -103,7 +101,7 @@ export const MembersList = () => {
|
||||
isLocked={!canInviteNewMember}
|
||||
/>
|
||||
)}
|
||||
{members?.map((member) => (
|
||||
{members.map((member) => (
|
||||
<MemberItem
|
||||
key={member.userId}
|
||||
email={member.email ?? ''}
|
||||
@@ -116,7 +114,7 @@ export const MembersList = () => {
|
||||
canEdit={canEdit}
|
||||
/>
|
||||
))}
|
||||
{invitations?.map((invitation) => (
|
||||
{invitations.map((invitation) => (
|
||||
<MemberItem
|
||||
key={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 { Plan } from 'db'
|
||||
import {
|
||||
BubbleBlockType,
|
||||
InputBlockType,
|
||||
@@ -52,11 +54,7 @@ export const BlockTypeLabel = ({ type }: Props): JSX.Element => {
|
||||
<Tooltip label="Upload Files">
|
||||
<HStack>
|
||||
<Text>File</Text>
|
||||
{isFreePlan(workspace) && (
|
||||
<Tag colorScheme="orange" size="sm">
|
||||
Pro
|
||||
</Tag>
|
||||
)}
|
||||
{isFreePlan(workspace) && <PlanTag plan={Plan.STARTER} />}
|
||||
</HStack>
|
||||
</Tooltip>
|
||||
)
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { Flex, FormLabel, Stack, Switch, useDisclosure } from '@chakra-ui/react'
|
||||
import {
|
||||
Flex,
|
||||
FormLabel,
|
||||
Stack,
|
||||
Switch,
|
||||
Tag,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react'
|
||||
import { ChangePlanModal } from 'components/shared/modals/ChangePlanModal'
|
||||
ChangePlanModal,
|
||||
LimitReached,
|
||||
} from 'components/shared/modals/ChangePlanModal'
|
||||
import { PlanTag } from 'components/shared/PlanTag'
|
||||
import { SwitchWithLabel } from 'components/shared/SwitchWithLabel'
|
||||
import { useWorkspace } from 'contexts/WorkspaceContext'
|
||||
import { Plan } from 'db'
|
||||
import { GeneralSettings } from 'models'
|
||||
import React from 'react'
|
||||
import { isFreePlan } from 'services/workspace'
|
||||
@@ -25,9 +23,9 @@ export const GeneralSettingsForm = ({
|
||||
}: Props) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const { workspace } = useWorkspace()
|
||||
const isUserFreePlan = isFreePlan(workspace)
|
||||
const isWorkspaceFreePlan = isFreePlan(workspace)
|
||||
const handleSwitchChange = () => {
|
||||
if (generalSettings?.isBrandingEnabled && isUserFreePlan) return
|
||||
if (generalSettings?.isBrandingEnabled && isWorkspaceFreePlan) return
|
||||
onGeneralSettingsChange({
|
||||
...generalSettings,
|
||||
isBrandingEnabled: !generalSettings?.isBrandingEnabled,
|
||||
@@ -56,15 +54,19 @@ export const GeneralSettingsForm = ({
|
||||
|
||||
return (
|
||||
<Stack spacing={6}>
|
||||
<ChangePlanModal isOpen={isOpen} onClose={onClose} />
|
||||
<ChangePlanModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
type={LimitReached.BRAND}
|
||||
/>
|
||||
<Flex
|
||||
justifyContent="space-between"
|
||||
align="center"
|
||||
onClick={isUserFreePlan ? onOpen : undefined}
|
||||
onClick={isWorkspaceFreePlan ? onOpen : undefined}
|
||||
>
|
||||
<FormLabel htmlFor="branding" mb="0">
|
||||
Typebot.io branding{' '}
|
||||
{isUserFreePlan && <Tag colorScheme="orange">Pro</Tag>}
|
||||
{isWorkspaceFreePlan && <PlanTag plan={Plan.STARTER} />}
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="branding"
|
||||
|
||||
@@ -36,11 +36,10 @@ export const EditableUrl = ({
|
||||
<Tooltip label="Edit">
|
||||
<EditablePreview
|
||||
mx={1}
|
||||
bgColor="blue.500"
|
||||
color="white"
|
||||
borderWidth="1px"
|
||||
px={3}
|
||||
rounded="md"
|
||||
cursor="pointer"
|
||||
cursor="text"
|
||||
display="flex"
|
||||
fontWeight="semibold"
|
||||
/>
|
||||
|
||||
@@ -4,18 +4,20 @@ import {
|
||||
HStack,
|
||||
IconButton,
|
||||
Stack,
|
||||
Tag,
|
||||
Wrap,
|
||||
Text,
|
||||
} from '@chakra-ui/react'
|
||||
import { TrashIcon } from 'assets/icons'
|
||||
import { UpgradeButton } from 'components/shared/buttons/UpgradeButton'
|
||||
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 { useWorkspace } from 'contexts/WorkspaceContext'
|
||||
import { Plan } from 'db'
|
||||
import React from 'react'
|
||||
import { parseDefaultPublicId } from 'services/typebots'
|
||||
import { isFreePlan } from 'services/workspace'
|
||||
import { isWorkspaceProPlan } from 'services/workspace'
|
||||
import { getViewerUrl, isDefined, isNotDefined } from 'utils'
|
||||
import { CustomDomainsDropdown } from './customDomain/CustomDomainsDropdown'
|
||||
import { EditableUrl } from './EditableUrl'
|
||||
@@ -80,19 +82,18 @@ export const ShareContent = () => {
|
||||
/>
|
||||
</HStack>
|
||||
)}
|
||||
{isFreePlan(workspace) ? (
|
||||
<UpgradeButton colorScheme="gray">
|
||||
<Text mr="2">Add my domain</Text>{' '}
|
||||
<Tag colorScheme="orange">Pro</Tag>
|
||||
</UpgradeButton>
|
||||
{isWorkspaceProPlan(workspace) &&
|
||||
isNotDefined(typebot?.customDomain) ? (
|
||||
<CustomDomainsDropdown
|
||||
onCustomDomainSelect={handleCustomDomainChange}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{isNotDefined(typebot?.customDomain) && (
|
||||
<CustomDomainsDropdown
|
||||
onCustomDomainSelect={handleCustomDomainChange}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
<UpgradeButton
|
||||
colorScheme="gray"
|
||||
limitReachedType={LimitReached.CUSTOM_DOMAIN}
|
||||
>
|
||||
<Text mr="2">Add my domain</Text> <PlanTag plan={Plan.PRO} />
|
||||
</UpgradeButton>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
computeSourceCoordinates,
|
||||
computeDropOffPath,
|
||||
} from 'services/graph'
|
||||
import { isFreePlan } from 'services/workspace'
|
||||
import { isWorkspaceProPlan } from 'services/workspace'
|
||||
import { byId, isDefined } from 'utils'
|
||||
|
||||
type Props = {
|
||||
@@ -28,7 +28,7 @@ export const DropOffEdge = ({
|
||||
const { sourceEndpoints, graphPosition } = useGraph()
|
||||
const { publishedTypebot } = useTypebot()
|
||||
|
||||
const isUserOnFreePlan = isFreePlan(workspace)
|
||||
const isProPlan = isWorkspaceProPlan(workspace)
|
||||
|
||||
const totalAnswers = useMemo(
|
||||
() => answersCounts.find((a) => a.groupId === groupId)?.totalAnswers,
|
||||
@@ -95,7 +95,7 @@ export const DropOffEdge = ({
|
||||
>
|
||||
<Tooltip
|
||||
label="Unlock Drop-off rate by upgrading to Pro plan"
|
||||
isDisabled={!isUserOnFreePlan}
|
||||
isDisabled={isProPlan}
|
||||
>
|
||||
<VStack
|
||||
bgColor={'red.500'}
|
||||
@@ -105,13 +105,28 @@ export const DropOffEdge = ({
|
||||
justifyContent="center"
|
||||
w="full"
|
||||
h="full"
|
||||
filter={isUserOnFreePlan ? 'blur(4px)' : ''}
|
||||
onClick={isUserOnFreePlan ? onUnlockProPlanClick : undefined}
|
||||
cursor={isUserOnFreePlan ? 'pointer' : 'auto'}
|
||||
onClick={isProPlan ? undefined : onUnlockProPlanClick}
|
||||
cursor={isProPlan ? 'auto' : 'pointer'}
|
||||
>
|
||||
<Text>{isUserOnFreePlan ? 'X' : dropOffRate}%</Text>
|
||||
<Text filter={isProPlan ? '' : 'blur(2px)'}>
|
||||
{isProPlan ? (
|
||||
dropOffRate
|
||||
) : (
|
||||
<Text as="span" filter="blur(2px)">
|
||||
X
|
||||
</Text>
|
||||
)}
|
||||
%
|
||||
</Text>
|
||||
<Tag colorScheme="red">
|
||||
{isUserOnFreePlan ? 'n' : totalDroppedUser} users
|
||||
{isProPlan ? (
|
||||
totalDroppedUser
|
||||
) : (
|
||||
<Text as="span" filter="blur(3px)" mr="1">
|
||||
NN
|
||||
</Text>
|
||||
)}{' '}
|
||||
users
|
||||
</Tag>
|
||||
</VStack>
|
||||
</Tooltip>
|
||||
|
||||
@@ -25,7 +25,6 @@ export const Edges = ({
|
||||
pos="absolute"
|
||||
left="0"
|
||||
top="0"
|
||||
pointerEvents="none"
|
||||
shapeRendering="geometricPrecision"
|
||||
>
|
||||
<DrawingEdge />
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Tag } from '@chakra-ui/react'
|
||||
import { Tag, TagProps } from '@chakra-ui/react'
|
||||
import { Plan } from 'db'
|
||||
|
||||
export const PlanTag = ({ plan }: { plan?: Plan }) => {
|
||||
export const PlanTag = ({ plan, ...props }: { plan?: Plan } & TagProps) => {
|
||||
switch (plan) {
|
||||
case Plan.LIFETIME:
|
||||
case Plan.PRO: {
|
||||
return (
|
||||
<Tag colorScheme="blue" data-testid="plan-tag">
|
||||
<Tag colorScheme="blue" data-testid="pro-plan-tag" {...props}>
|
||||
Pro
|
||||
</Tag>
|
||||
)
|
||||
@@ -14,14 +14,14 @@ export const PlanTag = ({ plan }: { plan?: Plan }) => {
|
||||
case Plan.OFFERED:
|
||||
case Plan.STARTER: {
|
||||
return (
|
||||
<Tag colorScheme="orange" data-testid="plan-tag">
|
||||
<Tag colorScheme="orange" data-testid="starter-plan-tag" {...props}>
|
||||
Starter
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<Tag colorScheme="gray" data-testid="plan-tag">
|
||||
<Tag colorScheme="gray" data-testid="free-plan-tag" {...props}>
|
||||
Free
|
||||
</Tag>
|
||||
)
|
||||
|
||||
@@ -5,9 +5,9 @@ import { isNotDefined } from 'utils'
|
||||
import { ChangePlanModal } 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 { workspace } = useWorkspace()
|
||||
return (
|
||||
@@ -18,7 +18,11 @@ export const UpgradeButton = ({ type, ...props }: Props) => {
|
||||
onClick={onOpen}
|
||||
>
|
||||
{props.children ?? 'Upgrade'}
|
||||
<ChangePlanModal isOpen={isOpen} onClose={onClose} type={type} />
|
||||
<ChangePlanModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
type={limitReachedType}
|
||||
/>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,9 +13,10 @@ import { ChangePlanForm } from 'components/shared/ChangePlanForm'
|
||||
|
||||
export enum LimitReached {
|
||||
BRAND = 'remove branding',
|
||||
CUSTOM_DOMAIN = 'add custom domain',
|
||||
CUSTOM_DOMAIN = 'add custom domains',
|
||||
FOLDER = 'create folders',
|
||||
FILE_INPUT = 'use file input blocks',
|
||||
ANALYTICS = 'unlock in-depth analytics',
|
||||
}
|
||||
|
||||
type ChangePlanModalProps = {
|
||||
|
||||
Reference in New Issue
Block a user