2
0

Add usage-based new pricing plans

This commit is contained in:
Baptiste Arnaud
2022-09-17 16:37:33 +02:00
committed by Baptiste Arnaud
parent 6a1eaea700
commit 898367a33b
144 changed files with 4631 additions and 1624 deletions

View File

@@ -0,0 +1,39 @@
<mjml>
<mj-head>
<mj-attributes>
<mj-all font-family="Helvetica Neue, Helvetica, Helvetica, Arial, sans-serif" font-size="16px" padding="0" line-height="23px"></mj-all>
<mj-section background-color="#ffffff" padding-bottom="20px"></mj-section>
<mj-text padding="10px 40px"></mj-text>
</mj-attributes>
<mj-style inline="inline">
.footer-link {
color: #A0AEC0
}
</mj-style>
</mj-head>
<mj-body background-color="#ffffff">
<mj-wrapper border="1px solid #E2E8F0">
<mj-section padding-bottom="0px">
<mj-column width="100%">
<mj-image src="https://typebot.s3.fr-par.scw.cloud/public/assets/yourBotIsFlyingEmailBanner.png" alt="header image" padding="0px"></mj-image>
</mj-column>
</mj-section>
<mj-section padding-top="20px">
<mj-column>
<mj-text>Your bots are chatting a lot. That's amazing. ❤️</mj-text>
<mj-text>This means you've almost reached your monthly chats limit. You currently reached 80% of ${readableChatsLimit}.</mj-text>
<mj-text>This limit will be reset on ${readableResetDate}.</mj-text>
<mj-text>Your bots won't start the chat if you reach the limit before this date. ⚠️</mj-text>
<mj-text>If you need more monthly responses, you will need to upgrade your plan.</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-button background-color="#0042da" color="white" href="${url}" font-weight="500" border-radius="5px">Upgrade workspace</mj-button>
</mj-column>
</mj-section>
</mj-wrapper>
</mj-body>
</mjml>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,38 @@
<mjml>
<mj-head>
<mj-attributes>
<mj-all font-family="Helvetica Neue, Helvetica, Helvetica, Arial, sans-serif" font-size="16px" padding="0" line-height="23px"></mj-all>
<mj-section background-color="#ffffff" padding-bottom="20px"></mj-section>
<mj-text padding="10px 40px"></mj-text>
</mj-attributes>
<mj-style inline="inline">
.footer-link {
color: #A0AEC0
}
</mj-style>
</mj-head>
<mj-body background-color="#ffffff">
<mj-wrapper border="1px solid #E2E8F0">
<mj-section padding-bottom="0px">
<mj-column width="100%">
<mj-image src="https://typebot.s3.fr-par.scw.cloud/public/assets/yourBotIsFlyingEmailBanner.png" alt="header image" padding="0px"></mj-image>
</mj-column>
</mj-section>
<mj-section padding-top="20px">
<mj-column>
<mj-text>Your bots are working a lot. That's amazing. 🤖</mj-text>
<mj-text>This means you've almost reached your storage limit. You currently reached 80% of your ${readableStorageLimit} limit.</mj-text>
<mj-text>Your bots won't collect new files once you reach the limit. ⚠️</mj-text>
<mj-text>To make sure it won't happen, you need to upgrade your plan or delete existing results to free up space.</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-button background-color="#0042da" color="white" href="${url}" font-weight="500" border-radius="5px">Upgrade workspace</mj-button>
</mj-column>
</mj-section>
</mj-wrapper>
</mj-body>
</mjml>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,37 @@
<mjml>
<mj-head>
<mj-attributes>
<mj-all font-family="Helvetica Neue, Helvetica, Helvetica, Arial, sans-serif" font-size="16px" padding="0" line-height="23px"></mj-all>
<mj-section background-color="#ffffff" padding-bottom="20px"></mj-section>
<mj-text padding="10px 40px"></mj-text>
</mj-attributes>
<mj-style inline="inline">
.footer-link {
color: #A0AEC0
}
</mj-style>
</mj-head>
<mj-body background-color="#ffffff">
<mj-wrapper border="1px solid #E2E8F0">
<mj-section padding-bottom="0px">
<mj-column width="100%">
<mj-image src="https://typebot.s3.fr-par.scw.cloud/public/assets/actionRequiredEmailBanner.png" alt="header image" padding="0px"></mj-image>
</mj-column>
</mj-section>
<mj-section padding-top="20px">
<mj-column>
<mj-text>It just happened, you've reached your monthly ${readableChatsLimit} chats limit 😮</mj-text>
<mj-text>It means your bots are closed until ${readableResetDate}.</mj-text>
<mj-text>If you'd like to continue chatting with your users this month, then you need to upgrade your plan. 🚀</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-button background-color="#0042da" color="white" href="${url}" font-weight="500" border-radius="5px">Upgrade workspace</mj-button>
</mj-column>
</mj-section>
</mj-wrapper>
</mj-body>
</mjml>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,37 @@
<mjml>
<mj-head>
<mj-attributes>
<mj-all font-family="Helvetica Neue, Helvetica, Helvetica, Arial, sans-serif" font-size="16px" padding="0" line-height="23px"></mj-all>
<mj-section background-color="#ffffff" padding-bottom="20px"></mj-section>
<mj-text padding="10px 40px"></mj-text>
</mj-attributes>
<mj-style inline="inline">
.footer-link {
color: #A0AEC0
}
</mj-style>
</mj-head>
<mj-body background-color="#ffffff">
<mj-wrapper border="1px solid #E2E8F0">
<mj-section padding-bottom="0px">
<mj-column width="100%">
<mj-image src="https://typebot.s3.fr-par.scw.cloud/public/assets/actionRequiredEmailBanner.png" alt="header image" padding="0px"></mj-image>
</mj-column>
</mj-section>
<mj-section padding-top="20px">
<mj-column>
<mj-text>It just happened, you've reached your ${readableStorageLimit} storage limit 😮</mj-text>
<mj-text>It means your bots won't collect new files from your users.</mj-text>
<mj-text>If you'd like to continue collecting files, then you need to upgrade your plan or remove existing results to free up space. 🚀</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-button background-color="#0042da" color="white" href="${url}" font-weight="500" border-radius="5px">Upgrade workspace</mj-button>
</mj-column>
</mj-section>
</mj-wrapper>
</mj-body>
</mjml>

File diff suppressed because one or more lines are too long

View File

@@ -65,12 +65,14 @@ export const TypebotPage = ({
const resultIdFromSession = getExistingResultFromSession()
if (resultIdFromSession) setResultId(resultIdFromSession)
else {
const { error, data: result } = await createResult(typebot.typebotId)
const { error, data } = await createResult(typebot.typebotId)
if (error) return setError(error)
if (result) {
setResultId(result.id)
if (data?.hasReachedLimit)
return setError(new Error('This bot is now closed.'))
if (data?.result) {
setResultId(data.result.id)
if (typebot.settings.general.isNewResultOnRefreshEnabled !== true)
setResultInSession(result.id)
setResultInSession(data.result.id)
}
}
}

View File

@@ -41,11 +41,14 @@
"@types/sanitize-html": "2.6.2",
"@typescript-eslint/eslint-plugin": "5.36.2",
"@typescript-eslint/parser": "5.36.2",
"dotenv": "^16.0.1",
"eslint": "8.23.0",
"eslint-config-next": "12.3.0",
"eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^4.6.0",
"google-auth-library": "^8.5.1",
"handlebars": "^4.7.7",
"mjml": "^4.13.0",
"models": "workspace:*",
"next-transpile-modules": "^9.0.0",
"papaparse": "^5.3.2",

View File

@@ -1,8 +1,22 @@
import { withSentry } from '@sentry/nextjs'
import { almostReachedStorageLimitEmail } from 'assets/emails/almostReachedStorageLimitEmail'
import { reachedStorageLimitEmail } from 'assets/emails/reachedStorageLimitEmail'
import { WorkspaceRole } from 'db'
import prisma from 'libs/prisma'
import { InputBlockType, PublicTypebot } from 'models'
import { NextApiRequest, NextApiResponse } from 'next'
import { badRequest, generatePresignedUrl, methodNotAllowed, byId } from 'utils'
import {
badRequest,
generatePresignedUrl,
methodNotAllowed,
byId,
getStorageLimit,
sendEmailNotification,
isDefined,
env,
} from 'utils'
const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
const handler = async (
req: NextApiRequest,
@@ -24,6 +38,7 @@ const handler = async (
const typebotId = req.query.typebotId as string
const blockId = req.query.blockId as string
if (!filePath) return badRequest(res, 'Missing filePath or fileType')
const hasReachedStorageLimit = await checkStorageLimit(typebotId)
const typebot = (await prisma.publicTypebot.findFirst({
where: { typebotId },
})) as unknown as PublicTypebot
@@ -42,9 +57,118 @@ const handler = async (
sizeLimit: sizeLimit * 1024 * 1024,
})
return res.status(200).send({ presignedUrl })
return res.status(200).send({ presignedUrl, hasReachedStorageLimit })
}
return methodNotAllowed(res)
}
const checkStorageLimit = async (typebotId: string) => {
const typebot = await prisma.typebot.findFirst({
where: { id: typebotId },
include: {
workspace: {
select: {
id: true,
additionalStorageIndex: true,
plan: true,
storageLimitFirstEmailSentAt: true,
storageLimitSecondEmailSentAt: true,
},
},
},
})
if (!typebot?.workspace) throw new Error('Workspace not found')
const { workspace } = typebot
const {
_sum: { storageUsed: totalStorageUsed },
} = await prisma.answer.aggregate({
where: {
storageUsed: { gt: 0 },
result: {
typebot: {
workspace: {
id: typebot?.workspaceId,
},
},
},
},
_sum: { storageUsed: true },
})
if (!totalStorageUsed) return false
const hasSentFirstEmail = workspace.storageLimitFirstEmailSentAt !== null
const hasSentSecondEmail = workspace.storageLimitSecondEmailSentAt !== null
const storageLimit = getStorageLimit(typebot.workspace)
if (
totalStorageUsed >= storageLimit * LIMIT_EMAIL_TRIGGER_PERCENT &&
!hasSentFirstEmail &&
env('E2E_TEST') !== 'true'
)
sendAlmostReachStorageLimitEmail({
workspaceId: workspace.id,
storageLimit,
})
if (
totalStorageUsed >= storageLimit &&
!hasSentSecondEmail &&
env('E2E_TEST') !== 'true'
)
sendReachStorageLimitEmail({
workspaceId: workspace.id,
storageLimit,
})
return (totalStorageUsed ?? 0) >= getStorageLimit(typebot?.workspace)
}
const sendAlmostReachStorageLimitEmail = async ({
workspaceId,
storageLimit,
}: {
workspaceId: string
storageLimit: number
}) => {
const members = await prisma.memberInWorkspace.findMany({
where: { role: WorkspaceRole.ADMIN, workspaceId },
include: { user: { select: { email: true } } },
})
const readableStorageLimit = `${storageLimit}GB`
await sendEmailNotification({
to: members.map((member) => member.user.email).filter(isDefined),
subject: "You're close to your storage limit",
html: almostReachedStorageLimitEmail({
readableStorageLimit,
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
}),
})
await prisma.workspace.update({
where: { id: workspaceId },
data: { storageLimitFirstEmailSentAt: new Date() },
})
}
const sendReachStorageLimitEmail = async ({
workspaceId,
storageLimit,
}: {
workspaceId: string
storageLimit: number
}) => {
const members = await prisma.memberInWorkspace.findMany({
where: { role: WorkspaceRole.ADMIN, workspaceId },
include: { user: { select: { email: true } } },
})
const readableStorageLimit = `${storageLimit}GB`
await sendEmailNotification({
to: members.map((member) => member.user.email).filter(isDefined),
subject: "You've hit your storage limit",
html: reachedStorageLimitEmail({
readableStorageLimit,
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
}),
})
await prisma.workspace.update({
where: { id: workspaceId },
data: { storageLimitSecondEmailSentAt: new Date() },
})
}
export default withSentry(handler)

View File

@@ -25,7 +25,7 @@ import {
saveSuccessLog,
} from 'services/api/utils'
import Mail from 'nodemailer/lib/mailer'
import { newLeadEmailContent } from 'assets/newLeadEmailContent'
import { newLeadEmailContent } from 'assets/emails/newLeadEmailContent'
const cors = initMiddleware(Cors())

View File

@@ -1,8 +1,20 @@
import { almostReachedChatsLimitEmail } from 'assets/emails/almostReachedChatsLimitEmail'
import { reachedSChatsLimitEmail } from 'assets/emails/reachedChatsLimitEmail'
import { Workspace, WorkspaceRole } from 'db'
import prisma from 'libs/prisma'
import { ResultWithAnswers } from 'models'
import { NextApiRequest, NextApiResponse } from 'next'
import { authenticateUser } from 'services/api/utils'
import { methodNotAllowed } from 'utils'
import {
env,
getChatsLimit,
isDefined,
methodNotAllowed,
parseNumberWithCommas,
sendEmailNotification,
} from 'utils'
const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') {
@@ -30,10 +42,150 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
typebotId,
isCompleted: false,
},
include: {
typebot: {
include: {
workspace: {
select: {
id: true,
plan: true,
additionalChatsIndex: true,
chatsLimitFirstEmailSentAt: true,
chatsLimitSecondEmailSentAt: true,
},
},
},
},
},
})
return res.send(result)
const hasReachedLimit = await checkChatsUsage(result.typebot.workspace)
res.send({ result, hasReachedLimit })
return
}
methodNotAllowed(res)
}
const checkChatsUsage = async (
workspace: Pick<
Workspace,
| 'id'
| 'plan'
| 'additionalChatsIndex'
| 'chatsLimitFirstEmailSentAt'
| 'chatsLimitSecondEmailSentAt'
>
) => {
const chatLimit = getChatsLimit(workspace)
if (chatLimit === -1) return
const now = new Date()
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
const firstDayOfNextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1)
const chatsCount = await prisma.result.count({
where: {
typebot: { workspaceId: workspace.id },
hasStarted: true,
createdAt: { gte: firstDayOfMonth, lte: lastDayOfMonth },
},
})
const hasSentFirstEmail =
workspace.chatsLimitFirstEmailSentAt !== null &&
workspace.chatsLimitFirstEmailSentAt < firstDayOfNextMonth &&
workspace.chatsLimitFirstEmailSentAt > firstDayOfMonth
const hasSentSecondEmail =
workspace.chatsLimitSecondEmailSentAt !== null &&
workspace.chatsLimitSecondEmailSentAt < firstDayOfNextMonth &&
workspace.chatsLimitSecondEmailSentAt > firstDayOfMonth
if (
chatsCount >= chatLimit * LIMIT_EMAIL_TRIGGER_PERCENT &&
!hasSentFirstEmail &&
env('E2E_TEST') !== 'true'
)
await sendAlmostReachChatsLimitEmail({
workspaceId: workspace.id,
chatLimit,
firstDayOfNextMonth,
})
if (
chatsCount >= chatLimit &&
!hasSentSecondEmail &&
env('E2E_TEST') !== 'true'
)
await sendReachedAlertEmail({
workspaceId: workspace.id,
chatLimit,
firstDayOfNextMonth,
})
return chatsCount >= chatLimit
}
const sendAlmostReachChatsLimitEmail = async ({
workspaceId,
chatLimit,
firstDayOfNextMonth,
}: {
workspaceId: string
chatLimit: number
firstDayOfNextMonth: Date
}) => {
const members = await prisma.memberInWorkspace.findMany({
where: { role: WorkspaceRole.ADMIN, workspaceId },
include: { user: { select: { email: true } } },
})
const readableChatsLimit = parseNumberWithCommas(chatLimit)
const readableResetDate = firstDayOfNextMonth
.toDateString()
.split(' ')
.slice(1, 4)
.join(' ')
await sendEmailNotification({
to: members.map((member) => member.user.email).filter(isDefined),
subject: "You've been invited to collaborate 🤝",
html: almostReachedChatsLimitEmail({
readableChatsLimit,
readableResetDate,
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
}),
})
await prisma.workspace.update({
where: { id: workspaceId },
data: { chatsLimitFirstEmailSentAt: new Date() },
})
}
const sendReachedAlertEmail = async ({
workspaceId,
chatLimit,
firstDayOfNextMonth,
}: {
workspaceId: string
chatLimit: number
firstDayOfNextMonth: Date
}) => {
const members = await prisma.memberInWorkspace.findMany({
where: { role: WorkspaceRole.ADMIN, workspaceId },
include: { user: { select: { email: true } } },
})
const readableChatsLimit = parseNumberWithCommas(chatLimit)
const readableResetDate = firstDayOfNextMonth
.toDateString()
.split(' ')
.slice(1, 4)
.join(' ')
await sendEmailNotification({
to: members.map((member) => member.user.email).filter(isDefined),
subject: "You've been invited to collaborate 🤝",
html: reachedSChatsLimitEmail({
readableChatsLimit,
readableResetDate,
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
}),
})
await prisma.workspace.update({
where: { id: workspaceId },
data: { chatsLimitSecondEmailSentAt: new Date() },
})
}
export default handler

View File

@@ -13,11 +13,14 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
let storageUsed = 0
if (uploadedFiles && answer.content.includes('http')) {
const fileUrls = answer.content.split(', ')
for (const url of fileUrls) {
const { headers } = await got(url)
const size = headers['content-length']
if (isNotDefined(size)) return
storageUsed += parseInt(size, 10)
const hasReachedStorageLimit = fileUrls[0] === null
if (!hasReachedStorageLimit) {
for (const url of fileUrls) {
const { headers } = await got(url)
const size = headers['content-length']
if (isNotDefined(size)) return
storageUsed += parseInt(size, 10)
}
}
}
const result = await prisma.answer.upsert({

View File

@@ -8,51 +8,88 @@ import {
Typebot,
Webhook,
} from 'models'
import { Plan, PrismaClient, WorkspaceRole } from 'db'
import { GraphNavigation, Plan, PrismaClient, WorkspaceRole } from 'db'
import { readFileSync } from 'fs'
import { encrypt } from 'utils'
import { createFakeResults, encrypt } from 'utils'
const prisma = new PrismaClient()
const proWorkspaceId = 'proWorkspaceViewer'
const userId = 'userId'
export const freeWorkspaceId = 'freeWorkspace'
export const starterWorkspaceId = 'starterWorkspace'
export const teardownDatabase = async () => {
try {
await prisma.workspace.deleteMany({
where: { members: { some: { userId: { in: ['proUser'] } } } },
})
await prisma.user.deleteMany({
where: { id: { in: ['proUser'] } },
})
} catch (err) {
console.error(err)
}
return
await prisma.workspace.deleteMany({
where: {
members: {
some: { userId },
},
},
})
await prisma.user.deleteMany({
where: { id: userId },
})
return prisma.webhook.deleteMany()
}
export const setupDatabase = () => createUser()
export const setupDatabase = async () => {
await createWorkspaces()
await createUser()
}
export const createUser = () =>
prisma.user.create({
export const createWorkspaces = async () =>
prisma.workspace.createMany({
data: [
{
id: freeWorkspaceId,
name: 'Free workspace',
plan: Plan.FREE,
},
{
id: starterWorkspaceId,
name: 'Starter workspace',
plan: Plan.STARTER,
},
],
})
export const createUser = async () => {
await prisma.user.create({
data: {
id: 'proUser',
id: userId,
email: 'user@email.com',
name: 'User',
apiTokens: { create: { token: 'userToken', name: 'default' } },
workspaces: {
create: {
role: WorkspaceRole.ADMIN,
workspace: {
create: {
id: proWorkspaceId,
name: 'Pro workspace',
plan: Plan.PRO,
name: 'John Doe',
graphNavigation: GraphNavigation.TRACKPAD,
apiTokens: {
createMany: {
data: [
{
name: 'Token 1',
token: 'jirowjgrwGREHEtoken1',
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),
},
],
},
},
},
})
await prisma.memberInWorkspace.createMany({
data: [
{ role: WorkspaceRole.ADMIN, userId, workspaceId: freeWorkspaceId },
{ role: WorkspaceRole.ADMIN, userId, workspaceId: starterWorkspaceId },
],
})
}
export const createWebhook = (typebotId: string, webhook?: Partial<Webhook>) =>
prisma.webhook.create({
@@ -66,12 +103,12 @@ export const createWebhook = (typebotId: string, webhook?: Partial<Webhook>) =>
export const createTypebots = async (partialTypebots: Partial<Typebot>[]) => {
await prisma.typebot.createMany({
data: partialTypebots.map(parseTestTypebot) as any[],
data: partialTypebots.map(parseTestTypebot),
})
return prisma.publicTypebot.createMany({
data: partialTypebots.map((t) =>
parseTypebotToPublicTypebot(t.id + '-published', parseTestTypebot(t))
) as any[],
),
})
}
@@ -107,7 +144,7 @@ const parseTestTypebot = (partialTypebot: Partial<Typebot>): Typebot => ({
id: partialTypebot.id ?? 'typebot',
folderId: null,
name: 'My typebot',
workspaceId: proWorkspaceId,
workspaceId: freeWorkspaceId,
icon: null,
theme: defaultTheme,
settings: defaultSettings,
@@ -170,7 +207,7 @@ export const importTypebotInDatabase = async (
const typebot: Typebot = {
...JSON.parse(readFileSync(path).toString()),
...updates,
workspaceId: proWorkspaceId,
workspaceId: starterWorkspaceId,
}
await prisma.typebot.create({
data: typebot,
@@ -183,39 +220,7 @@ export const importTypebotInDatabase = async (
})
}
export const createResults = async ({ typebotId }: { typebotId: string }) => {
await prisma.result.deleteMany()
await prisma.result.createMany({
data: [
...Array.from(Array(200)).map((_, idx) => {
const today = new Date()
const rand = Math.random()
return {
id: `result${idx}`,
typebotId,
createdAt: new Date(
today.setTime(today.getTime() + 1000 * 60 * 60 * 24 * idx)
),
isCompleted: rand > 0.5,
}
}),
],
})
return createAnswers()
}
const createAnswers = () => {
return prisma.answer.createMany({
data: [
...Array.from(Array(200)).map((_, idx) => ({
resultId: `result${idx}`,
content: `content${idx}`,
blockId: 'block1',
groupId: 'group1',
})),
],
})
}
export const createResults = createFakeResults(prisma)
export const createSmtpCredentials = (
id: string,
@@ -229,7 +234,7 @@ export const createSmtpCredentials = (
iv,
name: smtpData.from.email as string,
type: CredentialsType.SMTP,
workspaceId: proWorkspaceId,
workspaceId: freeWorkspaceId,
},
})
}

View File

@@ -14,7 +14,7 @@ test.beforeAll(async () => {
{ id: typebotId }
)
await createWebhook(typebotId)
await createResults({ typebotId })
await createResults({ typebotId, count: 20 })
} catch (err) {
console.log(err)
}

View File

@@ -3,12 +3,12 @@ import cuid from 'cuid'
import path from 'path'
import { parse } from 'papaparse'
import { typebotViewer } from '../services/selectorUtils'
import { importTypebotInDatabase } from '../services/database'
import { createResults, importTypebotInDatabase } from '../services/database'
import { readFileSync } from 'fs'
import { isDefined } from 'utils'
import { mockSessionApiCalls } from 'playwright/services/browser'
import { describe } from 'node:test'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
const THREE_GIGABYTES = 3 * 1024 * 1024 * 1024
test('should work as expected', async ({ page, browser }) => {
const typebotId = cuid()
@@ -85,3 +85,46 @@ test('should work as expected', async ({ page, browser }) => {
page2.locator('span:has-text("The specified key does not exist.")')
).toBeVisible()
})
describe('Storage limit is reached', () => {
const typebotId = cuid()
test.beforeAll(async () => {
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/fileUpload.json'),
{
id: typebotId,
publicId: `${typebotId}-public`,
}
)
await createResults({
typebotId,
count: 20,
fakeStorage: THREE_GIGABYTES,
})
})
test("shouldn't upload anything if limit has been reached", async ({
page,
}) => {
await page.goto(`/${typebotId}-public`)
await typebotViewer(page)
.locator(`input[type="file"]`)
.setInputFiles([
path.join(__dirname, '../fixtures/typebots/api.json'),
path.join(__dirname, '../fixtures/typebots/fileUpload.json'),
path.join(__dirname, '../fixtures/typebots/hugeGroup.json'),
])
await expect(typebotViewer(page).locator(`text="3"`)).toBeVisible()
await typebotViewer(page).locator('text="Upload 3 files"').click()
await expect(
typebotViewer(page).locator(`text="3 files uploaded"`)
).toBeVisible()
await page.evaluate(() =>
window.localStorage.setItem('workspaceId', 'starterWorkspace')
)
await page.goto(`${process.env.BUILDER_URL}/typebots/${typebotId}/results`)
await expect(page.locator('text="150%"')).toBeVisible()
await expect(page.locator('text="api.json"')).toBeHidden()
})
})

View File

@@ -0,0 +1,23 @@
import test, { expect } from '@playwright/test'
import {
createResults,
freeWorkspaceId,
importTypebotInDatabase,
} from '../services/database'
import cuid from 'cuid'
import path from 'path'
test('should not start if chat limit is reached', async ({ page }) => {
const typebotId = cuid()
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/fileUpload.json'),
{
id: typebotId,
publicId: `${typebotId}-public`,
workspaceId: freeWorkspaceId,
}
)
await createResults({ typebotId, count: 320 })
await page.goto(`/${typebotId}-public`)
await expect(page.locator('text="This bot is now closed."')).toBeVisible()
})

View File

@@ -2,7 +2,7 @@ import { Result } from 'db'
import { sendRequest } from 'utils'
export const createResult = async (typebotId: string) => {
return sendRequest<Result>({
return sendRequest<{ result: Result; hasReachedLimit: boolean }>({
url: `/api/typebots/${typebotId}/results`,
method: 'POST',
})