2
0

♻️ Add a new unlimited plan

Closes #273
This commit is contained in:
Baptiste Arnaud
2023-01-27 15:00:07 +01:00
parent 4f78dda640
commit 409e7643ad
12 changed files with 49 additions and 12 deletions

View File

@@ -34,6 +34,7 @@ export const BillingContent = () => {
</HStack> </HStack>
{workspace.plan !== Plan.CUSTOM && {workspace.plan !== Plan.CUSTOM &&
workspace.plan !== Plan.LIFETIME && workspace.plan !== Plan.LIFETIME &&
workspace.plan !== Plan.UNLIMITED &&
workspace.plan !== Plan.OFFERED && <ChangePlanForm />} workspace.plan !== Plan.OFFERED && <ChangePlanForm />}
</Stack> </Stack>

View File

@@ -136,7 +136,12 @@ export const UsageContent = ({ workspace }: Props) => {
> >
{storageToReadable(totalStorageUsed)} {storageToReadable(totalStorageUsed)}
</Skeleton> </Skeleton>
<Text>/ {workspaceStorageLimit} GB</Text> <Text>
/{' '}
{workspaceStorageLimit === -1
? 'Unlimited'
: `${workspaceStorageLimit} GB`}
</Text>
</HStack> </HStack>
</Flex> </Flex>
<Progress <Progress

View File

@@ -8,6 +8,7 @@ export const planColorSchemes: Record<Plan, ThemeTypings['colorSchemes']> = {
[Plan.STARTER]: 'orange', [Plan.STARTER]: 'orange',
[Plan.FREE]: 'gray', [Plan.FREE]: 'gray',
[Plan.CUSTOM]: 'yellow', [Plan.CUSTOM]: 'yellow',
[Plan.UNLIMITED]: 'yellow',
} }
export const PlanTag = ({ export const PlanTag = ({
@@ -64,12 +65,23 @@ export const PlanTag = ({
return ( return (
<Tag <Tag
colorScheme={planColorSchemes[Plan.CUSTOM]} colorScheme={planColorSchemes[Plan.CUSTOM]}
data-testid="free-plan-tag" data-testid="custom-plan-tag"
{...props} {...props}
> >
Custom Custom
</Tag> </Tag>
) )
} }
case Plan.UNLIMITED: {
return (
<Tag
colorScheme={planColorSchemes[Plan.UNLIMITED]}
data-testid="custom-unlimite-tag"
{...props}
>
Unlimited
</Tag>
)
}
} }
} }

View File

@@ -12,6 +12,8 @@ export const planToReadable = (plan?: Plan) => {
return 'Offered' return 'Offered'
case Plan.PRO: case Plan.PRO:
return 'Pro' return 'Pro'
case Plan.UNLIMITED:
return 'Unlimited'
} }
} }
@@ -22,4 +24,5 @@ export const isProPlan = (workspace?: Pick<Workspace, 'plan'>) =>
isDefined(workspace) && isDefined(workspace) &&
(workspace.plan === Plan.PRO || (workspace.plan === Plan.PRO ||
workspace.plan === Plan.LIFETIME || workspace.plan === Plan.LIFETIME ||
workspace.plan === Plan.CUSTOM) workspace.plan === Plan.CUSTOM ||
workspace.plan === Plan.UNLIMITED)

View File

@@ -106,7 +106,10 @@ export const MembersList = () => {
)} )}
{workspace && ( {workspace && (
<Heading fontSize="2xl"> <Heading fontSize="2xl">
Members ({currentMembersCount}/{getSeatsLimit(workspace)}) Members{' '}
{getSeatsLimit(workspace) === -1
? ''
: `(${currentMembersCount}/${getSeatsLimit(workspace)})`}
</Heading> </Heading>
)} )}
{workspace?.id && canEdit && ( {workspace?.id && canEdit && (

View File

@@ -12,8 +12,12 @@ export function checkCanInviteMember({
}) { }) {
if (!plan || !currentMembersCount) return false if (!plan || !currentMembersCount) return false
return ( const seatsLimit = getSeatsLimit({
getSeatsLimit({ plan, customSeatsLimit: customSeatsLimit ?? null }) > plan,
currentMembersCount customSeatsLimit: customSeatsLimit ?? null,
) })
if (seatsLimit === -1) return true
return seatsLimit > currentMembersCount
} }

View File

@@ -1,7 +1,7 @@
import { Plan } from 'db' import { Plan } from 'db'
export const parseWorkspaceDefaultPlan = (userEmail: string) => { export const parseWorkspaceDefaultPlan = (userEmail: string) => {
if (process.env.ADMIN_EMAIL === userEmail) return Plan.LIFETIME if (process.env.ADMIN_EMAIL === userEmail) return Plan.UNLIMITED
const defaultPlan = process.env.DEFAULT_WORKSPACE_PLAN as Plan | undefined const defaultPlan = process.env.DEFAULT_WORKSPACE_PLAN as Plan | undefined
if (defaultPlan && Object.values(Plan).includes(defaultPlan)) if (defaultPlan && Object.values(Plan).includes(defaultPlan))
return defaultPlan return defaultPlan

View File

@@ -16,7 +16,7 @@ import { SponsorButton } from '../../../src/js/SponsorButton.jsx'
| NEXTAUTH_URL | | The builder base URL. Should be the publicly accessible URL (i.e. `https://typebot.domain.com`) | | NEXTAUTH_URL | | The builder base URL. Should be the publicly accessible URL (i.e. `https://typebot.domain.com`) |
| NEXT_PUBLIC_VIEWER_URL | | The viewer base URL. Should be the publicly accessible URL (i.e. `https://bot.domain.com`) | | NEXT_PUBLIC_VIEWER_URL | | The viewer base URL. Should be the publicly accessible URL (i.e. `https://bot.domain.com`) |
| NEXTAUTH_URL_INTERNAL | | The internal builder base URL. You have to set it only when `NEXTAUTH_URL` can't be reached by your builder container / server. For a docker deployment, you should set it to `http://localhost:3000`. | | NEXTAUTH_URL_INTERNAL | | The internal builder base URL. You have to set it only when `NEXTAUTH_URL` can't be reached by your builder container / server. For a docker deployment, you should set it to `http://localhost:3000`. |
| DEFAULT_WORKSPACE_PLAN | FREE | Default workspace plan on user creation or when a user creates a new workspace. Possible values are `FREE`, `STARTER`, `PRO`, `LIFETIME`. The default plan for admin user is `LIFETIME` | | DEFAULT_WORKSPACE_PLAN | FREE | Default workspace plan on user creation or when a user creates a new workspace. Possible values are `FREE`, `STARTER`, `PRO`, `LIFETIME`, `UNLIMITED`. The default plan for admin user is `UNLIMITED` |
| DISABLE_SIGNUP | false | To disable new sign ups but still be able to sign in with existing users or admin email | | DISABLE_SIGNUP | false | To disable new sign ups but still be able to sign in with existing users or admin email |
## Email (Auth, notifications) ## Email (Auth, notifications)

View File

@@ -32,7 +32,7 @@ const handler = async (
const typebotId = req.query.typebotId as string const typebotId = req.query.typebotId as string
const blockId = req.query.blockId as string const blockId = req.query.blockId as string
if (!filePath) return badRequest(res, 'Missing filePath or fileType') if (!filePath) return badRequest(res, 'Missing filePath or fileType')
const hasReachedStorageLimit = await checkStorageLimit(typebotId) const hasReachedStorageLimit = await checkIfStorageLimitReached(typebotId)
const typebot = (await prisma.publicTypebot.findFirst({ const typebot = (await prisma.publicTypebot.findFirst({
where: { typebotId }, where: { typebotId },
})) as unknown as PublicTypebot })) as unknown as PublicTypebot
@@ -59,7 +59,9 @@ const handler = async (
return methodNotAllowed(res) return methodNotAllowed(res)
} }
const checkStorageLimit = async (typebotId: string): Promise<boolean> => { const checkIfStorageLimitReached = async (
typebotId: string
): Promise<boolean> => {
const typebot = await prisma.typebot.findUnique({ const typebot = await prisma.typebot.findUnique({
where: { id: typebotId }, where: { id: typebotId },
include: { include: {
@@ -94,6 +96,7 @@ const checkStorageLimit = async (typebotId: string): Promise<boolean> => {
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) const storageLimit = getStorageLimit(typebot.workspace)
if (storageLimit === -1) return false
const storageLimitBytes = storageLimit * 1024 * 1024 * 1024 const storageLimitBytes = storageLimit * 1024 * 1024 * 1024
if ( if (
totalStorageUsed >= storageLimitBytes * LIMIT_EMAIL_TRIGGER_PERCENT && totalStorageUsed >= storageLimitBytes * LIMIT_EMAIL_TRIGGER_PERCENT &&

View File

@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "Plan" ADD VALUE 'UNLIMITED';

View File

@@ -314,6 +314,7 @@ enum Plan {
LIFETIME LIFETIME
OFFERED OFFERED
CUSTOM CUSTOM
UNLIMITED
} }
enum CollaborationType { enum CollaborationType {

View File

@@ -33,6 +33,7 @@ export const chatsLimit = {
}, },
[Plan.OFFERED]: { totalIncluded: infinity }, [Plan.OFFERED]: { totalIncluded: infinity },
[Plan.LIFETIME]: { totalIncluded: infinity }, [Plan.LIFETIME]: { totalIncluded: infinity },
[Plan.UNLIMITED]: { totalIncluded: infinity },
} as const } as const
export const storageLimit = { export const storageLimit = {
@@ -60,6 +61,7 @@ export const storageLimit = {
}, },
[Plan.OFFERED]: { totalIncluded: 2 }, [Plan.OFFERED]: { totalIncluded: 2 },
[Plan.LIFETIME]: { totalIncluded: 10 }, [Plan.LIFETIME]: { totalIncluded: 10 },
[Plan.UNLIMITED]: { totalIncluded: infinity },
} as const } as const
export const seatsLimit = { export const seatsLimit = {
@@ -75,6 +77,7 @@ export const seatsLimit = {
}, },
[Plan.OFFERED]: { totalIncluded: 2 }, [Plan.OFFERED]: { totalIncluded: 2 },
[Plan.LIFETIME]: { totalIncluded: 8 }, [Plan.LIFETIME]: { totalIncluded: 8 },
[Plan.UNLIMITED]: { totalIncluded: infinity },
} as const } as const
export const getChatsLimit = ({ export const getChatsLimit = ({