🛂 (billing) Add isPastDue field in workspace (#1046)
Closes #1039 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Workspaces now include additional status indicator: `isPastDue`. - New pages for handling workspaces that are past due. Meaning, an invoice is unpaid. - **Bug Fixes** - Fixed issues with workspace status checks and redirections for suspended workspaces. - **Refactor** - Refactored workspace-related API functions to accommodate new status fields. - Improved permission checks for reading and writing typebots based on workspace status. - **Chores** - Database schema updated to include `isPastDue` field for workspaces. - Implemented new webhook event handling for subscription and invoice updates. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -56,7 +56,17 @@ export const getLinkedTypebots = authenticatedProcedure
|
||||
variables: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
workspaceId: true,
|
||||
workspace: {
|
||||
select: {
|
||||
isQuarantined: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
collaborators: {
|
||||
select: {
|
||||
type: true,
|
||||
@@ -97,7 +107,17 @@ export const getLinkedTypebots = authenticatedProcedure
|
||||
variables: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
workspaceId: true,
|
||||
workspace: {
|
||||
select: {
|
||||
isQuarantined: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
collaborators: {
|
||||
select: {
|
||||
type: true,
|
||||
|
||||
@@ -32,6 +32,17 @@ export const getCollaborators = authenticatedProcedure
|
||||
},
|
||||
include: {
|
||||
collaborators: true,
|
||||
workspace: {
|
||||
select: {
|
||||
isQuarantined: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if (
|
||||
|
||||
@@ -36,8 +36,19 @@ export const deleteResults = authenticatedProcedure
|
||||
id: typebotId,
|
||||
},
|
||||
select: {
|
||||
workspaceId: true,
|
||||
groups: true,
|
||||
workspace: {
|
||||
select: {
|
||||
isQuarantined: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
role: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
collaborators: {
|
||||
select: {
|
||||
userId: true,
|
||||
|
||||
@@ -33,8 +33,18 @@ export const getResult = authenticatedProcedure
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
workspaceId: true,
|
||||
groups: true,
|
||||
workspace: {
|
||||
select: {
|
||||
isQuarantined: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
collaborators: {
|
||||
select: {
|
||||
userId: true,
|
||||
|
||||
@@ -28,8 +28,18 @@ export const getResultLogs = authenticatedProcedure
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
workspaceId: true,
|
||||
groups: true,
|
||||
workspace: {
|
||||
select: {
|
||||
isQuarantined: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
collaborators: {
|
||||
select: {
|
||||
userId: true,
|
||||
|
||||
@@ -44,7 +44,6 @@ export const getResults = authenticatedProcedure
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
workspaceId: true,
|
||||
groups: true,
|
||||
collaborators: {
|
||||
select: {
|
||||
@@ -52,6 +51,17 @@ export const getResults = authenticatedProcedure
|
||||
type: true,
|
||||
},
|
||||
},
|
||||
workspace: {
|
||||
select: {
|
||||
isQuarantined: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if (!typebot || (await isReadTypebotForbidden(typebot, user)))
|
||||
|
||||
@@ -81,7 +81,7 @@ export const processTelemetryEvent = authenticatedProcedure
|
||||
client.capture({
|
||||
distinctId: event.userId,
|
||||
event: event.name,
|
||||
properties: event.data,
|
||||
properties: 'data' in event ? event.data : undefined,
|
||||
groups,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -33,8 +33,19 @@ export const deleteTypebot = authenticatedProcedure
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
workspaceId: true,
|
||||
groups: true,
|
||||
workspace: {
|
||||
select: {
|
||||
isQuarantined: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
role: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
collaborators: {
|
||||
select: {
|
||||
userId: true,
|
||||
|
||||
@@ -52,6 +52,17 @@ export const getPublishedTypebot = authenticatedProcedure
|
||||
include: {
|
||||
collaborators: true,
|
||||
publishedTypebot: true,
|
||||
workspace: {
|
||||
select: {
|
||||
isQuarantined: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if (
|
||||
|
||||
@@ -41,6 +41,17 @@ export const getTypebot = authenticatedProcedure
|
||||
},
|
||||
include: {
|
||||
collaborators: true,
|
||||
workspace: {
|
||||
select: {
|
||||
isQuarantined: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if (
|
||||
|
||||
@@ -46,6 +46,14 @@ export const publishTypebot = authenticatedProcedure
|
||||
workspace: {
|
||||
select: {
|
||||
plan: true,
|
||||
isQuarantined: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
role: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -32,6 +32,18 @@ export const unpublishTypebot = authenticatedProcedure
|
||||
include: {
|
||||
collaborators: true,
|
||||
publishedTypebot: true,
|
||||
workspace: {
|
||||
select: {
|
||||
isQuarantined: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
role: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if (!existingTypebot?.publishedTypebot)
|
||||
|
||||
@@ -79,7 +79,6 @@ export const updateTypebot = authenticatedProcedure
|
||||
id: true,
|
||||
customDomain: true,
|
||||
publicId: true,
|
||||
workspaceId: true,
|
||||
collaborators: {
|
||||
select: {
|
||||
userId: true,
|
||||
@@ -88,7 +87,16 @@ export const updateTypebot = authenticatedProcedure
|
||||
},
|
||||
workspace: {
|
||||
select: {
|
||||
id: true,
|
||||
plan: true,
|
||||
isQuarantined: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
role: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
updatedAt: true,
|
||||
@@ -160,7 +168,7 @@ export const updateTypebot = authenticatedProcedure
|
||||
selectedThemeTemplateId: typebot.selectedThemeTemplateId,
|
||||
events: typebot.events ?? undefined,
|
||||
groups: typebot.groups
|
||||
? await sanitizeGroups(existingTypebot.workspaceId)(typebot.groups)
|
||||
? await sanitizeGroups(existingTypebot.workspace.id)(typebot.groups)
|
||||
: undefined,
|
||||
theme: typebot.theme ? typebot.theme : undefined,
|
||||
settings: typebot.settings
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import { env } from '@typebot.io/env'
|
||||
import { CollaboratorsOnTypebots, User } from '@typebot.io/prisma'
|
||||
import { Typebot } from '@typebot.io/schemas'
|
||||
import {
|
||||
CollaboratorsOnTypebots,
|
||||
User,
|
||||
Workspace,
|
||||
MemberInWorkspace,
|
||||
} from '@typebot.io/prisma'
|
||||
|
||||
export const isReadTypebotForbidden = async (
|
||||
typebot: Pick<Typebot, 'workspaceId'> & {
|
||||
typebot: {
|
||||
collaborators: Pick<CollaboratorsOnTypebots, 'userId'>[]
|
||||
} & {
|
||||
workspace: Pick<Workspace, 'isQuarantined' | 'isPastDue'> & {
|
||||
members: Pick<MemberInWorkspace, 'userId'>[]
|
||||
}
|
||||
},
|
||||
user: Pick<User, 'email' | 'id'>
|
||||
) => {
|
||||
if (
|
||||
env.ADMIN_EMAIL === user.email ||
|
||||
typebot.collaborators.find(
|
||||
) =>
|
||||
typebot.workspace.isQuarantined ||
|
||||
typebot.workspace.isPastDue ||
|
||||
(env.ADMIN_EMAIL !== user.email &&
|
||||
!typebot.collaborators.some(
|
||||
(collaborator) => collaborator.userId === user.id
|
||||
)
|
||||
)
|
||||
return false
|
||||
const memberInWorkspace = await prisma.memberInWorkspace.findFirst({
|
||||
where: {
|
||||
workspaceId: typebot.workspaceId,
|
||||
userId: user.id,
|
||||
},
|
||||
})
|
||||
return memberInWorkspace === null
|
||||
}
|
||||
) &&
|
||||
!typebot.workspace.members.some((member) => member.userId === user.id))
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import {
|
||||
CollaborationType,
|
||||
CollaboratorsOnTypebots,
|
||||
MemberInWorkspace,
|
||||
User,
|
||||
Workspace,
|
||||
} from '@typebot.io/prisma'
|
||||
import { Typebot } from '@typebot.io/schemas'
|
||||
import { isNotDefined } from '@typebot.io/lib'
|
||||
|
||||
export const isWriteTypebotForbidden = async (
|
||||
typebot: Pick<Typebot, 'workspaceId'> & {
|
||||
typebot: {
|
||||
collaborators: Pick<CollaboratorsOnTypebots, 'userId' | 'type'>[]
|
||||
} & {
|
||||
workspace: Pick<Workspace, 'isQuarantined' | 'isPastDue'> & {
|
||||
members: Pick<MemberInWorkspace, 'userId' | 'role'>[]
|
||||
}
|
||||
},
|
||||
user: Pick<User, 'id'>
|
||||
) => {
|
||||
if (
|
||||
typebot.collaborators.find(
|
||||
(collaborator) => collaborator.userId === user.id
|
||||
)?.type === CollaborationType.WRITE
|
||||
return (
|
||||
typebot.workspace.isQuarantined ||
|
||||
typebot.workspace.isPastDue ||
|
||||
!(
|
||||
typebot.collaborators.find(
|
||||
(collaborator) => collaborator.userId === user.id
|
||||
)?.type === CollaborationType.WRITE &&
|
||||
typebot.workspace.members.some(
|
||||
(m) => m.userId === user.id && m.role !== 'GUEST'
|
||||
)
|
||||
)
|
||||
)
|
||||
return false
|
||||
const memberInWorkspace = await prisma.memberInWorkspace.findFirst({
|
||||
where: {
|
||||
workspaceId: typebot.workspaceId,
|
||||
userId: user.id,
|
||||
},
|
||||
})
|
||||
return isNotDefined(memberInWorkspace) || memberInWorkspace.role === 'GUEST'
|
||||
}
|
||||
|
||||
@@ -147,7 +147,19 @@ const parseFilePath = async ({
|
||||
id: input.typebotId,
|
||||
},
|
||||
select: {
|
||||
workspaceId: true,
|
||||
workspace: {
|
||||
select: {
|
||||
plan: true,
|
||||
isQuarantined: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
role: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
collaborators: {
|
||||
select: {
|
||||
userId: true,
|
||||
|
||||
@@ -57,7 +57,17 @@ export const startWhatsAppPreview = authenticatedProcedure
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
workspaceId: true,
|
||||
workspace: {
|
||||
select: {
|
||||
isQuarantined: true,
|
||||
isPastDue: true,
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
collaborators: {
|
||||
select: {
|
||||
userId: true,
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { byId, isNotDefined } from '@typebot.io/lib'
|
||||
import { byId } from '@typebot.io/lib'
|
||||
import { WorkspaceRole } from '@typebot.io/prisma'
|
||||
import { useRouter } from 'next/router'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
@@ -136,16 +136,20 @@ export const WorkspaceProvider = ({
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (isNotDefined(workspace?.isSuspended)) return
|
||||
if (workspace?.isSuspended && pathname !== '/suspended') push('/suspended')
|
||||
}, [pathname, push, workspace?.isSuspended])
|
||||
if (workspace?.isSuspended) {
|
||||
if (pathname === '/suspended') return
|
||||
push('/suspended')
|
||||
return
|
||||
}
|
||||
if (workspace?.isPastDue) {
|
||||
if (pathname === '/past-due') return
|
||||
push('/past-due')
|
||||
return
|
||||
}
|
||||
}, [pathname, push, workspace?.isPastDue, workspace?.isSuspended])
|
||||
|
||||
const switchWorkspace = (workspaceId: string) => {
|
||||
setWorkspaceIdInLocalStorage(workspaceId)
|
||||
if (pathname === '/suspended') {
|
||||
window.location.href = '/typebots'
|
||||
return
|
||||
}
|
||||
setWorkspaceId(workspaceId)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user