feat: ✨ Add collaboration
This commit is contained in:
27
apps/builder/services/api/emails.ts
Normal file
27
apps/builder/services/api/emails.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { createTransport } from 'nodemailer'
|
||||
|
||||
export const sendEmailNotification = ({
|
||||
to,
|
||||
subject,
|
||||
content,
|
||||
}: {
|
||||
to: string
|
||||
subject: string
|
||||
content: string
|
||||
}) => {
|
||||
const transporter = createTransport({
|
||||
host: process.env.AUTH_EMAIL_SERVER_HOST,
|
||||
port: Number(process.env.AUTH_EMAIL_SERVER_PORT),
|
||||
auth: {
|
||||
user: process.env.AUTH_EMAIL_SERVER_USER,
|
||||
pass: process.env.AUTH_EMAIL_SERVER_PASSWORD,
|
||||
},
|
||||
})
|
||||
|
||||
return transporter.sendMail({
|
||||
from: `"${process.env.AUTH_EMAIL_FROM_NAME}" <${process.env.AUTH_EMAIL_FROM_EMAIL}>`,
|
||||
to,
|
||||
subject,
|
||||
html: content,
|
||||
})
|
||||
}
|
10
apps/builder/services/api/utils.ts
Normal file
10
apps/builder/services/api/utils.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { User } from 'db'
|
||||
import { NextApiRequest } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
|
||||
export const getAuthenticatedUser = async (
|
||||
req: NextApiRequest
|
||||
): Promise<User | undefined> => {
|
||||
const session = await getSession({ req })
|
||||
return session?.user as User | undefined
|
||||
}
|
@ -15,7 +15,7 @@ export const useFolders = ({
|
||||
const { data, error, mutate } = useSWR<{ folders: DashboardFolder[] }, Error>(
|
||||
`/api/folders?${params}`,
|
||||
fetcher,
|
||||
{ dedupingInterval: 0 }
|
||||
{ dedupingInterval: process.env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined }
|
||||
)
|
||||
if (error) onError(error)
|
||||
return {
|
||||
|
48
apps/builder/services/typebots/collaborators.ts
Normal file
48
apps/builder/services/typebots/collaborators.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { CollaboratorsOnTypebots } from 'db'
|
||||
import { fetcher } from 'services/utils'
|
||||
import useSWR from 'swr'
|
||||
import { sendRequest } from 'utils'
|
||||
|
||||
export type Collaborator = CollaboratorsOnTypebots & {
|
||||
user: {
|
||||
name: string | null
|
||||
image: string | null
|
||||
email: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export const useCollaborators = ({
|
||||
typebotId,
|
||||
onError,
|
||||
}: {
|
||||
typebotId?: string
|
||||
onError: (error: Error) => void
|
||||
}) => {
|
||||
const { data, error, mutate } = useSWR<
|
||||
{ collaborators: Collaborator[] },
|
||||
Error
|
||||
>(typebotId ? `/api/typebots/${typebotId}/collaborators` : null, fetcher)
|
||||
if (error) onError(error)
|
||||
return {
|
||||
collaborators: data?.collaborators,
|
||||
isLoading: !error && !data,
|
||||
mutate,
|
||||
}
|
||||
}
|
||||
|
||||
export const updateCollaborator = (
|
||||
typebotId: string,
|
||||
userId: string,
|
||||
updates: Partial<CollaboratorsOnTypebots>
|
||||
) =>
|
||||
sendRequest({
|
||||
method: 'PUT',
|
||||
url: `/api/typebots/${typebotId}/collaborators/${userId}`,
|
||||
body: updates,
|
||||
})
|
||||
|
||||
export const deleteCollaborator = (typebotId: string, userId: string) =>
|
||||
sendRequest({
|
||||
method: 'DELETE',
|
||||
url: `/api/typebots/${typebotId}/collaborators/${userId}`,
|
||||
})
|
2
apps/builder/services/typebots/index.ts
Normal file
2
apps/builder/services/typebots/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './results'
|
||||
export * from './typebots'
|
51
apps/builder/services/typebots/invitations.ts
Normal file
51
apps/builder/services/typebots/invitations.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { CollaborationType, Invitation } from 'db'
|
||||
import { fetcher } from 'services/utils'
|
||||
import useSWR from 'swr'
|
||||
import { sendRequest } from 'utils'
|
||||
|
||||
export const useInvitations = ({
|
||||
typebotId,
|
||||
onError,
|
||||
}: {
|
||||
typebotId?: string
|
||||
onError: (error: Error) => void
|
||||
}) => {
|
||||
const { data, error, mutate } = useSWR<{ invitations: Invitation[] }, Error>(
|
||||
typebotId ? `/api/typebots/${typebotId}/invitations` : null,
|
||||
fetcher,
|
||||
{ dedupingInterval: process.env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined }
|
||||
)
|
||||
if (error) onError(error)
|
||||
return {
|
||||
invitations: data?.invitations,
|
||||
isLoading: !error && !data,
|
||||
mutate,
|
||||
}
|
||||
}
|
||||
|
||||
export const sendInvitation = (
|
||||
typebotId: string,
|
||||
{ email, type }: { email: string; type: CollaborationType }
|
||||
) =>
|
||||
sendRequest({
|
||||
method: 'POST',
|
||||
url: `/api/typebots/${typebotId}/invitations`,
|
||||
body: { email, type },
|
||||
})
|
||||
|
||||
export const updateInvitation = (
|
||||
typebotId: string,
|
||||
userId: string,
|
||||
updates: Partial<Invitation>
|
||||
) =>
|
||||
sendRequest({
|
||||
method: 'PUT',
|
||||
url: `/api/typebots/${typebotId}/invitations/${userId}`,
|
||||
body: updates,
|
||||
})
|
||||
|
||||
export const deleteInvitation = (typebotId: string, userId: string) =>
|
||||
sendRequest({
|
||||
method: 'DELETE',
|
||||
url: `/api/typebots/${typebotId}/invitations/${userId}`,
|
||||
})
|
@ -1,9 +1,9 @@
|
||||
import { ResultWithAnswers, VariableWithValue } from 'models'
|
||||
import useSWRInfinite from 'swr/infinite'
|
||||
import { fetcher } from './utils'
|
||||
import { stringify } from 'qs'
|
||||
import { Answer } from 'db'
|
||||
import { isDefined, sendRequest } from 'utils'
|
||||
import { fetcher } from 'services/utils'
|
||||
|
||||
const paginationLimit = 50
|
||||
|
@ -39,7 +39,7 @@ import {
|
||||
import shortId, { generate } from 'short-uuid'
|
||||
import { Typebot } from 'models'
|
||||
import useSWR from 'swr'
|
||||
import { fetcher, omit, toKebabCase } from './utils'
|
||||
import { fetcher, omit, toKebabCase } from '../utils'
|
||||
import {
|
||||
isBubbleStepType,
|
||||
stepTypeHasItems,
|
||||
@ -49,7 +49,7 @@ import {
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { stringify } from 'qs'
|
||||
import { isChoiceInput, isConditionStep, sendRequest } from 'utils'
|
||||
import { parseBlocksToPublicBlocks } from './publicTypebot'
|
||||
import { parseBlocksToPublicBlocks } from '../publicTypebot'
|
||||
|
||||
export type TypebotInDashboard = Pick<
|
||||
Typebot,
|
||||
@ -66,7 +66,9 @@ export const useTypebots = ({
|
||||
const { data, error, mutate } = useSWR<
|
||||
{ typebots: TypebotInDashboard[] },
|
||||
Error
|
||||
>(`/api/typebots?${params}`, fetcher, { dedupingInterval: 0 })
|
||||
>(`/api/typebots?${params}`, fetcher, {
|
||||
dedupingInterval: process.env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined,
|
||||
})
|
||||
if (error) onError(error)
|
||||
return {
|
||||
typebots: data?.typebots,
|
||||
@ -109,7 +111,6 @@ export const duplicateTypebot = async (typebotId: string) => {
|
||||
},
|
||||
'id'
|
||||
)
|
||||
console.log(duplicatedTypebot)
|
||||
return sendRequest<Typebot>({
|
||||
url: `/api/typebots`,
|
||||
method: 'POST',
|
@ -1,7 +1,7 @@
|
||||
import { Credentials } from 'models'
|
||||
import useSWR from 'swr'
|
||||
import { sendRequest } from 'utils'
|
||||
import { fetcher } from './utils'
|
||||
import { fetcher } from '../utils'
|
||||
|
||||
export const useCredentials = ({
|
||||
userId,
|
@ -2,7 +2,7 @@ import { CustomDomain } from 'db'
|
||||
import { Credentials } from 'models'
|
||||
import useSWR from 'swr'
|
||||
import { sendRequest } from 'utils'
|
||||
import { fetcher } from './utils'
|
||||
import { fetcher } from '../utils'
|
||||
|
||||
export const useCustomDomains = ({
|
||||
userId,
|
3
apps/builder/services/user/index.ts
Normal file
3
apps/builder/services/user/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './user'
|
||||
export * from './customDomains'
|
||||
export * from './credentials'
|
42
apps/builder/services/user/sharedTypebots.ts
Normal file
42
apps/builder/services/user/sharedTypebots.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { Typebot } from 'models'
|
||||
import { fetcher } from 'services/utils'
|
||||
import useSWR from 'swr'
|
||||
import { isNotDefined } from 'utils'
|
||||
|
||||
export const useSharedTypebotsCount = ({
|
||||
userId,
|
||||
onError,
|
||||
}: {
|
||||
userId?: string
|
||||
onError: (error: Error) => void
|
||||
}) => {
|
||||
const { data, error, mutate } = useSWR<{ count: number }, Error>(
|
||||
userId ? `/api/users/${userId}/sharedTypebots?count=true` : null,
|
||||
fetcher
|
||||
)
|
||||
if (error) onError(error)
|
||||
return {
|
||||
totalSharedTypebots: data?.count ?? 0,
|
||||
isLoading: !error && isNotDefined(data?.count),
|
||||
mutate,
|
||||
}
|
||||
}
|
||||
|
||||
export const useSharedTypebots = ({
|
||||
userId,
|
||||
onError,
|
||||
}: {
|
||||
userId?: string
|
||||
onError: (error: Error) => void
|
||||
}) => {
|
||||
const { data, error, mutate } = useSWR<
|
||||
{ sharedTypebots: Pick<Typebot, 'name' | 'id' | 'publishedTypebotId'>[] },
|
||||
Error
|
||||
>(userId ? `/api/users/${userId}/sharedTypebots` : null, fetcher)
|
||||
if (error) onError(error)
|
||||
return {
|
||||
sharedTypebots: data?.sharedTypebots,
|
||||
isLoading: !error && isNotDefined(data),
|
||||
mutate,
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user