diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index d67d2840d98..7b1fd66e790 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -86,6 +86,14 @@ export type CreateRefreshTokenInput = { expiresAt: Scalars['DateTime']; }; +export type CreateWorkspaceInput = { + allowImpersonation?: InputMaybe; + displayName?: InputMaybe; + domainName?: InputMaybe; + inviteHash?: InputMaybe; + logo?: InputMaybe; +}; + export type CursorPaging = { /** Paginate after opaque cursor */ after?: InputMaybe; @@ -265,6 +273,11 @@ export type MutationCreateOneRefreshTokenArgs = { }; +export type MutationCreateWorkspaceArgs = { + data: CreateWorkspaceInput; +}; + + export type MutationDeleteOneObjectArgs = { input: DeleteOneObjectInput; }; @@ -859,6 +872,13 @@ export type UploadProfilePictureMutationVariables = Exact<{ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProfilePicture: string }; +export type CreateWorkspaceMutationVariables = Exact<{ + input: CreateWorkspaceInput; +}>; + + +export type CreateWorkspaceMutation = { __typename?: 'Mutation', createWorkspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, allowImpersonation: boolean, subscriptionStatus: string } }; + export type DeleteCurrentWorkspaceMutationVariables = Exact<{ [key: string]: never; }>; @@ -1668,6 +1688,44 @@ export function useUploadProfilePictureMutation(baseOptions?: Apollo.MutationHoo export type UploadProfilePictureMutationHookResult = ReturnType; export type UploadProfilePictureMutationResult = Apollo.MutationResult; export type UploadProfilePictureMutationOptions = Apollo.BaseMutationOptions; +export const CreateWorkspaceDocument = gql` + mutation CreateWorkspace($input: CreateWorkspaceInput!) { + createWorkspace(data: $input) { + id + domainName + displayName + logo + allowImpersonation + subscriptionStatus + } +} + `; +export type CreateWorkspaceMutationFn = Apollo.MutationFunction; + +/** + * __useCreateWorkspaceMutation__ + * + * To run a mutation, you first call `useCreateWorkspaceMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateWorkspaceMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createWorkspaceMutation, { data, loading, error }] = useCreateWorkspaceMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useCreateWorkspaceMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(CreateWorkspaceDocument, options); + } +export type CreateWorkspaceMutationHookResult = ReturnType; +export type CreateWorkspaceMutationResult = Apollo.MutationResult; +export type CreateWorkspaceMutationOptions = Apollo.BaseMutationOptions; export const DeleteCurrentWorkspaceDocument = gql` mutation DeleteCurrentWorkspace { deleteCurrentWorkspace { diff --git a/packages/twenty-front/src/modules/workspace/graphql/mutations/createWorkspace.ts b/packages/twenty-front/src/modules/workspace/graphql/mutations/createWorkspace.ts new file mode 100644 index 00000000000..645f0ae0e54 --- /dev/null +++ b/packages/twenty-front/src/modules/workspace/graphql/mutations/createWorkspace.ts @@ -0,0 +1,14 @@ +import { gql } from '@apollo/client'; + +export const CREATE_WORKSPACE = gql` + mutation CreateWorkspace($input: CreateWorkspaceInput!) { + createWorkspace(data: $input) { + id + domainName + displayName + logo + allowImpersonation + subscriptionStatus + } + } +`; diff --git a/packages/twenty-front/src/pages/auth/CreateWorkspace.tsx b/packages/twenty-front/src/pages/auth/CreateWorkspace.tsx index e38d21376b7..aba0c1ef6aa 100644 --- a/packages/twenty-front/src/pages/auth/CreateWorkspace.tsx +++ b/packages/twenty-front/src/pages/auth/CreateWorkspace.tsx @@ -12,13 +12,14 @@ import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus'; import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader'; +import { AppPath } from '@/types/AppPath'; import { PageHotkeyScope } from '@/types/PageHotkeyScope'; import { H2Title } from '@/ui/display/typography/components/H2Title'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { MainButton } from '@/ui/input/button/components/MainButton'; import { TextInput } from '@/ui/input/components/TextInput'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { useUpdateWorkspaceMutation } from '~/generated/graphql'; +import { useCreateWorkspaceMutation } from '~/generated/graphql'; const StyledContentContainer = styled.div` width: 100%; @@ -48,7 +49,7 @@ export const CreateWorkspace = () => { const onboardingStatus = useOnboardingStatus(); const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); - const [updateWorkspace] = useUpdateWorkspaceMutation(); + const [createWorkspace] = useCreateWorkspaceMutation(); // Form const { @@ -67,7 +68,7 @@ export const CreateWorkspace = () => { const onSubmit: SubmitHandler
= useCallback( async (data) => { try { - const result = await updateWorkspace({ + const result = await createWorkspace({ variables: { input: { displayName: data.name, @@ -75,20 +76,20 @@ export const CreateWorkspace = () => { }, }); setCurrentWorkspace({ - id: result.data?.updateWorkspace?.id ?? '', + id: result.data?.createWorkspace?.id ?? '', displayName: data.name, subscriptionStatus: - result.data?.updateWorkspace?.subscriptionStatus ?? 'incomplete', + result.data?.createWorkspace?.subscriptionStatus ?? 'incomplete', allowImpersonation: - result.data?.updateWorkspace?.allowImpersonation ?? false, + result.data?.createWorkspace?.allowImpersonation ?? false, }); - if (result.errors || !result.data?.updateWorkspace) { + if (result.errors || !result.data?.createWorkspace) { throw result.errors ?? new Error('Unknown error'); } setTimeout(() => { - navigate('/create/profile'); + navigate(AppPath.CreateProfile); }, 20); } catch (error: any) { enqueueSnackBar(error?.message, { @@ -96,7 +97,7 @@ export const CreateWorkspace = () => { }); } }, - [enqueueSnackBar, navigate, setCurrentWorkspace, updateWorkspace], + [enqueueSnackBar, navigate, setCurrentWorkspace, createWorkspace], ); const handleKeyDown = (event: React.KeyboardEvent) => { diff --git a/packages/twenty-server/src/core/user/services/user.service.ts b/packages/twenty-server/src/core/user/services/user.service.ts index 4dc2574b726..f7ddaed9a12 100644 --- a/packages/twenty-server/src/core/user/services/user.service.ts +++ b/packages/twenty-server/src/core/user/services/user.service.ts @@ -91,8 +91,13 @@ export class UserService extends TypeOrmQueryService { async updateUser( userId: string, data: QueryDeepPartialEntity, - ): Promise { + ): Promise { await this.userRepository.update(userId, data); + + return await this.userRepository.findOneOrFail({ + where: { id: userId }, + relations: ['defaultWorkspace'], + }); } async deleteUser(userId: string): Promise { diff --git a/packages/twenty-server/src/core/workspace/dtos/create-workspace-input.ts b/packages/twenty-server/src/core/workspace/dtos/create-workspace-input.ts new file mode 100644 index 00000000000..2b656a8e7a1 --- /dev/null +++ b/packages/twenty-server/src/core/workspace/dtos/create-workspace-input.ts @@ -0,0 +1,31 @@ +import { Field, InputType } from '@nestjs/graphql'; + +import { IsBoolean, IsOptional, IsString } from 'class-validator'; + +@InputType() +export class CreateWorkspaceInput { + @Field({ nullable: true }) + @IsString() + @IsOptional() + domainName?: string; + + @Field({ nullable: true }) + @IsString() + @IsOptional() + displayName?: string; + + @Field({ nullable: true }) + @IsString() + @IsOptional() + logo?: string; + + @Field({ nullable: true }) + @IsString() + @IsOptional() + inviteHash?: string; + + @Field({ nullable: true }) + @IsBoolean() + @IsOptional() + allowImpersonation?: boolean; +} diff --git a/packages/twenty-server/src/core/workspace/services/workspace.service.ts b/packages/twenty-server/src/core/workspace/services/workspace.service.ts index 6242232f889..2a761177dbe 100644 --- a/packages/twenty-server/src/core/workspace/services/workspace.service.ts +++ b/packages/twenty-server/src/core/workspace/services/workspace.service.ts @@ -10,6 +10,7 @@ import { WorkspaceManagerService } from 'src/workspace/workspace-manager/workspa import { Workspace } from 'src/core/workspace/workspace.entity'; import { User } from 'src/core/user/user.entity'; import { UserService } from 'src/core/user/services/user.service'; +import { CreateWorkspaceInput } from 'src/core/workspace/dtos/create-workspace-input'; export class WorkspaceService extends TypeOrmQueryService { constructor( @@ -21,18 +22,24 @@ export class WorkspaceService extends TypeOrmQueryService { super(workspaceRepository); } - async createWorkspace(user: User) { + async createWorkspace(user: User, data: CreateWorkspaceInput) { const workspaceToCreate = this.workspaceRepository.create({ displayName: '', domainName: '', inviteHash: v4(), subscriptionStatus: 'active', + ...data, }); const workspace = await this.workspaceRepository.save(workspaceToCreate); - await this.userService.updateUser(user.id, { defaultWorkspace: workspace }); - await this.workspaceManagerService.init(user.defaultWorkspace.id); - await this.userService.createWorkspaceMember(user); + const updatedUser = await this.userService.updateUser(user.id, { + defaultWorkspace: workspace, + }); + + await this.workspaceManagerService.init(workspace.id); + await this.userService.createWorkspaceMember(updatedUser); + + return workspace; } async deleteWorkspace(id: string) { diff --git a/packages/twenty-server/src/core/workspace/workspace.resolver.ts b/packages/twenty-server/src/core/workspace/workspace.resolver.ts index 138b60b4845..6e3a851bc8f 100644 --- a/packages/twenty-server/src/core/workspace/workspace.resolver.ts +++ b/packages/twenty-server/src/core/workspace/workspace.resolver.ts @@ -14,6 +14,7 @@ import { UpdateWorkspaceInput } from 'src/core/workspace/dtos/update-workspace-i import { EnvironmentService } from 'src/integrations/environment/environment.service'; import { AuthUser } from 'src/decorators/auth-user.decorator'; import { User } from 'src/core/user/user.entity'; +import { CreateWorkspaceInput } from 'src/core/workspace/dtos/create-workspace-input'; import { Workspace } from './workspace.entity'; @@ -39,10 +40,11 @@ export class WorkspaceResolver { @Mutation(() => Workspace) @UseGuards(JwtAuthGuard) - async createWorkspace(@AuthUser() user: User): Promise { - await this.workspaceService.createWorkspace(user); - - return user.defaultWorkspace; + async createWorkspace( + @Args('data') data: CreateWorkspaceInput, + @AuthUser() user: User, + ): Promise { + return await this.workspaceService.createWorkspace(user, data); } @Mutation(() => Workspace)