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

@@ -14,18 +14,23 @@
"@rollup/plugin-commonjs": "22.0.2",
"@rollup/plugin-node-resolve": "^14.0.1",
"@rollup/plugin-typescript": "8.5.0",
"@types/nodemailer": "6.4.5",
"aws-sdk": "2.1213.0",
"db": "workspace:*",
"models": "workspace:*",
"next": "12.3.0",
"nodemailer": "^6.7.8",
"rollup": "2.79.0",
"rollup-plugin-dts": "^4.2.2",
"rollup-plugin-peer-deps-external": "^2.2.4",
"tslib": "^2.4.0",
"typescript": "^4.8.3",
"aws-sdk": "2.1213.0",
"models": "workspace:*",
"next": "12.3.0"
"typescript": "^4.8.3"
},
"peerDependencies": {
"aws-sdk": "^2.1152.0",
"db": "workspace:*",
"models": "workspace:*",
"next": "^12.0.0"
"next": "^12.0.0",
"nodemailer": "^6.7.8"
}
}

View File

@@ -1,2 +1,3 @@
export * from './utils'
export * from './storage'
export * from './sendEmailNotification'

View File

@@ -0,0 +1,18 @@
import { createTransport, SendMailOptions } from 'nodemailer'
import { env } from '../utils'
export const sendEmailNotification = (props: Omit<SendMailOptions, 'from'>) => {
const transporter = createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
auth: {
user: process.env.SMTP_USERNAME,
pass: process.env.SMTP_PASSWORD,
},
})
return transporter.sendMail({
from: env('SMTP_FROM'),
...props,
})
}

View File

@@ -2,3 +2,5 @@ export * from './utils'
export * from './api'
export * from './encryption'
export * from './results'
export * from './pricing'
export * from './playwright'

View File

@@ -0,0 +1,60 @@
import { PrismaClient } from 'db'
type CreateFakeResultsProps = {
typebotId: string
count: number
idPrefix?: string
isChronological?: boolean
fakeStorage?: number
}
export const createFakeResults =
(prisma: PrismaClient) =>
async ({
count,
idPrefix = '',
typebotId,
isChronological = true,
fakeStorage,
}: CreateFakeResultsProps) => {
await prisma.result.createMany({
data: [
...Array.from(Array(count)).map((_, idx) => {
const today = new Date()
const rand = Math.random()
return {
id: `${idPrefix}-result${idx}`,
typebotId,
createdAt: isChronological
? new Date(
today.setTime(today.getTime() + 1000 * 60 * 60 * 24 * idx)
)
: new Date(),
isCompleted: rand > 0.5,
hasStarted: true,
}
}),
],
})
return createAnswers(prisma)({ idPrefix, fakeStorage, count })
}
const createAnswers =
(prisma: PrismaClient) =>
({
count,
idPrefix,
fakeStorage,
}: Pick<CreateFakeResultsProps, 'fakeStorage' | 'idPrefix' | 'count'>) => {
return prisma.answer.createMany({
data: [
...Array.from(Array(count)).map((_, idx) => ({
resultId: `${idPrefix}-result${idx}`,
content: `content${idx}`,
blockId: 'block1',
groupId: 'block1',
storageUsed: fakeStorage ? Math.round(fakeStorage / count) : null,
})),
],
})
}

View File

@@ -0,0 +1,85 @@
import { Plan, Workspace } from 'db'
const infinity = -1
export const prices = {
[Plan.STARTER]: 39,
[Plan.PRO]: 89,
} as const
export const chatsLimit = {
[Plan.FREE]: { totalIncluded: 300 },
[Plan.STARTER]: {
totalIncluded: 2000,
increaseStep: {
amount: 500,
price: 10,
},
},
[Plan.PRO]: {
totalIncluded: 10000,
increaseStep: {
amount: 1000,
price: 10,
},
},
[Plan.OFFERED]: { totalIncluded: infinity },
[Plan.LIFETIME]: { totalIncluded: infinity },
} as const
export const storageLimit = {
[Plan.FREE]: { totalIncluded: 0 },
[Plan.STARTER]: {
totalIncluded: 2,
increaseStep: {
amount: 1,
price: 2,
},
},
[Plan.PRO]: {
totalIncluded: 10,
increaseStep: {
amount: 1,
price: 2,
},
},
[Plan.OFFERED]: { totalIncluded: 2 },
[Plan.LIFETIME]: { totalIncluded: 10 },
} as const
export const seatsLimit = {
[Plan.FREE]: { totalIncluded: 0 },
[Plan.STARTER]: {
totalIncluded: 2,
},
[Plan.PRO]: {
totalIncluded: 5,
},
[Plan.OFFERED]: { totalIncluded: 2 },
[Plan.LIFETIME]: { totalIncluded: 8 },
} as const
export const getChatsLimit = ({
plan,
additionalChatsIndex,
}: Pick<Workspace, 'additionalChatsIndex' | 'plan'>) => {
const { totalIncluded } = chatsLimit[plan]
const increaseStep =
plan === Plan.STARTER || plan === Plan.PRO
? chatsLimit[plan].increaseStep
: { amount: 0 }
if (totalIncluded === infinity) return infinity
return totalIncluded + increaseStep.amount * additionalChatsIndex
}
export const getStorageLimit = ({
plan,
additionalStorageIndex,
}: Pick<Workspace, 'additionalStorageIndex' | 'plan'>) => {
const { totalIncluded } = storageLimit[plan]
const increaseStep =
plan === Plan.STARTER || plan === Plan.PRO
? storageLimit[plan].increaseStep
: { amount: 0 }
return totalIncluded + increaseStep.amount * additionalStorageIndex
}

View File

@@ -200,7 +200,7 @@ type UploadFileProps = {
}[]
onUploadProgress?: (percent: number) => void
}
type UrlList = string[]
type UrlList = (string | null)[]
export const uploadFiles = async ({
basePath = '/api',
@@ -214,6 +214,7 @@ export const uploadFiles = async ({
i += 1
const { data } = await sendRequest<{
presignedUrl: { url: string; fields: any }
hasReachedStorageLimit: boolean
}>(
`${basePath}/storage/upload-url?filePath=${encodeURIComponent(
path
@@ -223,18 +224,21 @@ export const uploadFiles = async ({
if (!data?.presignedUrl) continue
const { url, fields } = data.presignedUrl
const formData = new FormData()
Object.entries({ ...fields, file }).forEach(([key, value]) => {
formData.append(key, value as string | Blob)
})
const upload = await fetch(url, {
method: 'POST',
body: formData,
})
if (data.hasReachedStorageLimit) urls.push(null)
else {
const formData = new FormData()
Object.entries({ ...fields, file }).forEach(([key, value]) => {
formData.append(key, value as string | Blob)
})
const upload = await fetch(url, {
method: 'POST',
body: formData,
})
if (!upload.ok) continue
if (!upload.ok) continue
urls.push(`${url.split('?')[0]}/${path}`)
urls.push(`${url.split('?')[0]}/${path}`)
}
}
return urls
}
@@ -276,3 +280,6 @@ export const getViewerUrl = (props?: {
: process.env.NEXT_PUBLIC_VERCEL_URL)
)
}
export const parseNumberWithCommas = (num: number) =>
num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')