2
0

feat(editor): Team workspaces

This commit is contained in:
Baptiste Arnaud
2022-05-13 15:22:44 -07:00
parent 6c2986590b
commit f0fdf08b00
132 changed files with 3354 additions and 1228 deletions

View File

@ -40,7 +40,6 @@ import useUndo from 'services/utils/useUndo'
import { useDebounce } from 'use-debounce'
import { itemsAction, ItemsActions } from './actions/items'
import { dequal } from 'dequal'
import { User } from 'db'
import { saveWebhook } from 'services/webhook'
import { stringify } from 'qs'
import cuid from 'cuid'
@ -63,7 +62,6 @@ const typebotContext = createContext<
typebot?: Typebot
publishedTypebot?: PublicTypebot
linkedTypebots?: Typebot[]
owner?: User
webhooks: Webhook[]
isReadOnly?: boolean
isPublished: boolean
@ -108,22 +106,15 @@ export const TypebotContext = ({
status: 'error',
})
const {
typebot,
publishedTypebot,
owner,
webhooks,
isReadOnly,
isLoading,
mutate,
} = useFetchedTypebot({
typebotId,
onError: (error) =>
toast({
title: 'Error while fetching typebot',
description: error.message,
}),
})
const { typebot, publishedTypebot, webhooks, isReadOnly, isLoading, mutate } =
useFetchedTypebot({
typebotId,
onError: (error) =>
toast({
title: 'Error while fetching typebot',
description: error.message,
}),
})
const [
{ present: localTypebot },
@ -150,6 +141,7 @@ export const TypebotContext = ({
)
const { typebots: linkedTypebots } = useLinkedTypebots({
workspaceId: localTypebot?.workspaceId ?? undefined,
typebotId,
typebotIds: linkedTypebotIds,
onError: (error) =>
@ -373,7 +365,6 @@ export const TypebotContext = ({
typebot: localTypebot,
publishedTypebot,
linkedTypebots,
owner,
webhooks: webhooks ?? [],
isReadOnly,
isSavingLoading,
@ -415,17 +406,17 @@ export const useFetchedTypebot = ({
typebot: Typebot
webhooks: Webhook[]
publishedTypebot?: PublicTypebot
owner?: User
isReadOnly?: boolean
},
Error
>(`/api/typebots/${typebotId}`, fetcher, { dedupingInterval: 0 })
>(`/api/typebots/${typebotId}`, fetcher, {
dedupingInterval: process.env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined,
})
if (error) onError(error)
return {
typebot: data?.typebot,
webhooks: data?.webhooks,
publishedTypebot: data?.publishedTypebot,
owner: data?.owner,
isReadOnly: data?.isReadOnly,
isLoading: !error && !data,
mutate,
@ -433,15 +424,17 @@ export const useFetchedTypebot = ({
}
const useLinkedTypebots = ({
workspaceId,
typebotId,
typebotIds,
onError,
}: {
workspaceId?: string
typebotId?: string
typebotIds?: string[]
onError: (error: Error) => void
}) => {
const params = stringify({ typebotIds }, { indices: false })
const params = stringify({ typebotIds, workspaceId }, { indices: false })
const { data, error, mutate } = useSWR<
{
typebots: Typebot[]

View File

@ -21,6 +21,7 @@ const userContext = createContext<{
isSaving: boolean
hasUnsavedChanges: boolean
isOAuthProvider: boolean
currentWorkspaceId?: string
updateUser: (newUser: Partial<User>) => void
saveUser: (newUser?: Partial<User>) => Promise<void>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@ -35,6 +36,7 @@ export const UserContext = ({ children }: { children: ReactNode }) => {
position: 'top-right',
status: 'error',
})
const [currentWorkspaceId, setCurrentWorkspaceId] = useState<string>()
const [isSaving, setIsSaving] = useState(false)
const isOAuthProvider = useMemo(
@ -49,6 +51,9 @@ export const UserContext = ({ children }: { children: ReactNode }) => {
useEffect(() => {
if (isDefined(user) || isNotDefined(session)) return
setCurrentWorkspaceId(
localStorage.getItem('currentWorkspaceId') ?? undefined
)
const parsedUser = session.user as User
setUser(parsedUser)
if (parsedUser?.id) setSentryUser({ id: parsedUser.id })
@ -96,6 +101,7 @@ export const UserContext = ({ children }: { children: ReactNode }) => {
isLoading: status === 'loading',
hasUnsavedChanges,
isOAuthProvider,
currentWorkspaceId,
updateUser,
saveUser,
}}

View File

@ -0,0 +1,144 @@
import {
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from 'react'
import { byId } from 'utils'
import { MemberInWorkspace, Plan, Workspace, WorkspaceRole } from 'db'
import {
createNewWorkspace,
useWorkspaces,
updateWorkspace as patchWorkspace,
} from 'services/workspace/workspace'
import { useUser } from './UserContext'
import { useTypebot } from './TypebotContext'
export type WorkspaceWithMembers = Workspace & { members: MemberInWorkspace[] }
const workspaceContext = createContext<{
workspaces?: WorkspaceWithMembers[]
isLoading: boolean
workspace?: WorkspaceWithMembers
canEdit: boolean
currentRole?: WorkspaceRole
switchWorkspace: (workspaceId: string) => void
createWorkspace: (name?: string) => Promise<void>
updateWorkspace: (
workspaceId: string,
updates: Partial<Workspace>
) => Promise<void>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
}>({})
export const WorkspaceContext = ({ children }: { children: ReactNode }) => {
const { user } = useUser()
const userId = user?.id
const { typebot } = useTypebot()
const { workspaces, isLoading, mutate } = useWorkspaces({ userId })
const [currentWorkspace, setCurrentWorkspace] =
useState<WorkspaceWithMembers>()
const canEdit =
workspaces
?.find(byId(currentWorkspace?.id))
?.members.find((m) => m.userId === userId)?.role === WorkspaceRole.ADMIN
const currentRole = currentWorkspace?.members.find(
(m) => m.userId === userId
)?.role
console.log(workspaces)
useEffect(() => {
if (!workspaces || workspaces.length === 0) return
const lastWorspaceId = localStorage.getItem('workspaceId')
const defaultWorkspace = lastWorspaceId
? workspaces.find(byId(lastWorspaceId))
: workspaces.find((w) =>
w.members.some(
(m) => m.userId === userId && m.role === WorkspaceRole.ADMIN
)
)
setCurrentWorkspace(defaultWorkspace ?? workspaces[0])
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [workspaces?.length])
useEffect(() => {
if (!currentWorkspace?.id) return
localStorage.setItem('workspaceId', currentWorkspace.id)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentWorkspace?.id])
useEffect(() => {
if (
!typebot?.workspaceId ||
!currentWorkspace ||
typebot.workspaceId === currentWorkspace.id
)
return
switchWorkspace(typebot.workspaceId)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [typebot?.workspaceId])
const switchWorkspace = (workspaceId: string) =>
setCurrentWorkspace(workspaces?.find(byId(workspaceId)))
const createWorkspace = async (name?: string) => {
if (!workspaces) return
const { data, error } = await createNewWorkspace({
name: name ? `${name}'s workspace` : 'My workspace',
plan: Plan.FREE,
})
if (error || !data) return
const { workspace } = data
const newWorkspace = {
...workspace,
members: [
{
role: WorkspaceRole.ADMIN,
userId: userId as string,
workspaceId: workspace.id as string,
},
],
}
mutate({
workspaces: [...workspaces, newWorkspace],
})
setCurrentWorkspace(newWorkspace)
}
const updateWorkspace = async (
workspaceId: string,
updates: Partial<Workspace>
) => {
const { data } = await patchWorkspace({ id: workspaceId, ...updates })
if (!data || !currentWorkspace) return
setCurrentWorkspace({ ...currentWorkspace, ...updates })
mutate({
workspaces: (workspaces ?? []).map((w) =>
w.id === workspaceId ? { ...data.workspace, members: w.members } : w
),
})
}
return (
<workspaceContext.Provider
value={{
workspaces,
workspace: currentWorkspace,
isLoading,
canEdit,
currentRole,
switchWorkspace,
createWorkspace,
updateWorkspace,
}}
>
{children}
</workspaceContext.Provider>
)
}
export const useWorkspace = () => useContext(workspaceContext)