[WIP] Whole FE migrated (#2517)

* Wip

* WIP

* Removed concole log

* Add relations to workspace init (#2511)

* Add relations to workspace init

* remove logs

* update prefill

* add missing isSystem

* comment relation fields

* Migrate v2 core models to graphql schema (#2509)

* migrate v2 core models to graphql schema

* Migrate to new workspace member schema

* Continue work

* migrated-main

* Finished accountOwner nested field integration on companies

* Introduce bug

* Fix

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Charles Bochet
2023-11-15 15:46:06 +01:00
committed by GitHub
parent 1f49ed2acf
commit 6129444c5c
129 changed files with 3468 additions and 1497 deletions

View File

@ -19,7 +19,7 @@ const documents = {
"\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: ID!\n $updatePayload: UpdateObjectInput!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n }\n }\n": types.UpdateOneObjectMetadataItemDocument,
"\n mutation DeleteOneObjectMetadataItem($idToDelete: ID!) {\n deleteOneObject(input: { id: $idToDelete }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n }\n }\n": types.DeleteOneObjectMetadataItemDocument,
"\n mutation DeleteOneFieldMetadataItem($idToDelete: ID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n placeholder\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n }\n }\n": types.DeleteOneFieldMetadataItemDocument,
"\n query ObjectMetadataItems($filter: objectFilter) {\n objects(paging: { first: 1000 }, filter: $filter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n fields(paging: { first: 1000 }) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n placeholder\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n }\n toRelationMetadata {\n id\n relationType\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n": types.ObjectMetadataItemsDocument,
"\n query ObjectMetadataItems($filter: objectFilter) {\n objects(paging: { first: 1000 }, filter: $filter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n fields(paging: { first: 1000 }) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n }\n toRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n": types.ObjectMetadataItemsDocument,
};
/**
@ -63,7 +63,7 @@ export function graphql(source: "\n mutation DeleteOneFieldMetadataItem($idToDe
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query ObjectMetadataItems($filter: objectFilter) {\n objects(paging: { first: 1000 }, filter: $filter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n fields(paging: { first: 1000 }) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n placeholder\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n }\n toRelationMetadata {\n id\n relationType\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n"): (typeof documents)["\n query ObjectMetadataItems($filter: objectFilter) {\n objects(paging: { first: 1000 }, filter: $filter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n fields(paging: { first: 1000 }) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n placeholder\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n }\n toRelationMetadata {\n id\n relationType\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n"];
export function graphql(source: "\n query ObjectMetadataItems($filter: objectFilter) {\n objects(paging: { first: 1000 }, filter: $filter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n fields(paging: { first: 1000 }) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n }\n toRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n"): (typeof documents)["\n query ObjectMetadataItems($filter: objectFilter) {\n objects(paging: { first: 1000 }, filter: $filter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n fields(paging: { first: 1000 }) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n }\n toRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n"];
export function graphql(source: string) {
return (documents as any)[source] ?? {};

File diff suppressed because one or more lines are too long

View File

@ -16,6 +16,7 @@ export type Scalars = {
ConnectionCursor: any;
DateTime: string;
JSON: any;
JSONObject: any;
Upload: any;
};
@ -966,6 +967,15 @@ export type CompanyWhereUniqueInput = {
id?: InputMaybe<Scalars['String']>;
};
export type CreateOneRefreshTokenV2Input = {
/** The record to create */
refreshTokenV2: CreateRefreshTokenInput;
};
export type CreateRefreshTokenInput = {
expiresAt: Scalars['DateTime'];
};
export enum Currency {
Aed = 'AED',
Afn = 'AFN',
@ -1398,6 +1408,8 @@ export type Mutation = {
createOnePerson: Person;
createOnePipelineProgress: PipelineProgress;
createOnePipelineStage: PipelineStage;
createOneRefreshTokenV2: RefreshTokenV2;
createOneRelation: Relation;
createOneWebHook: WebHook;
deleteCurrentWorkspace: Workspace;
deleteFavorite: Favorite;
@ -1410,6 +1422,7 @@ export type Mutation = {
deleteOnePipelineStage: PipelineStage;
deleteOneWebHook: WebHook;
deleteUserAccount: User;
deleteUserV2: UserV2;
deleteWorkspaceMember: WorkspaceMember;
impersonate: Verify;
renewToken: AuthTokens;
@ -1430,6 +1443,7 @@ export type Mutation = {
uploadImage: Scalars['String'];
uploadPersonPicture: Scalars['String'];
uploadProfilePicture: Scalars['String'];
uploadProfilePictureV2: Scalars['String'];
uploadWorkspaceLogo: Scalars['String'];
verify: Verify;
};
@ -1515,6 +1529,11 @@ export type MutationCreateOnePipelineStageArgs = {
};
export type MutationCreateOneRefreshTokenV2Args = {
input: CreateOneRefreshTokenV2Input;
};
export type MutationCreateOneWebHookArgs = {
data: WebHookCreateInput;
};
@ -1660,6 +1679,11 @@ export type MutationUploadProfilePictureArgs = {
};
export type MutationUploadProfilePictureV2Args = {
file: Scalars['Upload'];
};
export type MutationUploadWorkspaceLogoArgs = {
file: Scalars['Upload'];
};
@ -2409,6 +2433,7 @@ export type Query = {
checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValid;
clientConfig: ClientConfig;
currentUser: User;
currentUserV2: UserV2;
currentWorkspace: Workspace;
field: Field;
fields: FieldConnection;
@ -2428,6 +2453,8 @@ export type Query = {
findWorkspaceFromInviteHash: Workspace;
object: Object;
objects: ObjectConnection;
relation: Relation;
relations: RelationConnection;
};
@ -2560,32 +2587,6 @@ export enum QueryMode {
Insensitive = 'insensitive'
}
export type RefreshToken = {
__typename?: 'RefreshToken';
createdAt: Scalars['DateTime'];
expiresAt: Scalars['DateTime'];
id: Scalars['ID'];
updatedAt: Scalars['DateTime'];
};
export type RefreshTokenConnection = {
__typename?: 'RefreshTokenConnection';
/** Array of edges. */
edges: Array<RefreshTokenEdge>;
/** Paging information */
pageInfo: PageInfo;
/** Fetch total count of records */
totalCount: Scalars['Int'];
};
export type RefreshTokenEdge = {
__typename?: 'RefreshTokenEdge';
/** Cursor for this node. */
cursor: Scalars['ConnectionCursor'];
/** The node containing the RefreshToken */
node: RefreshToken;
};
export type RelationConnection = {
__typename?: 'RelationConnection';
/** Array of edges. */
@ -2644,15 +2645,6 @@ export type Support = {
supportFrontChatId?: Maybe<Scalars['String']>;
};
export type TUser = {
__typename?: 'TUser';
email: Scalars['String'];
emailVerified: Scalars['Boolean'];
firstName?: Maybe<Scalars['String']>;
id: Scalars['ID'];
lastName?: Maybe<Scalars['String']>;
};
export type Telemetry = {
__typename?: 'Telemetry';
anonymizationEnabled: Scalars['Boolean'];
@ -2669,6 +2661,7 @@ export type User = {
comments?: Maybe<Array<Comment>>;
companies?: Maybe<Array<Company>>;
createdAt: Scalars['DateTime'];
defaultWorkspaceId?: Maybe<Scalars['String']>;
disabled: Scalars['Boolean'];
displayName: Scalars['String'];
email: Scalars['String'];
@ -2680,11 +2673,8 @@ export type User = {
locale: Scalars['String'];
metadata?: Maybe<Scalars['JSON']>;
phoneNumber?: Maybe<Scalars['String']>;
settings: UserSettings;
settingsId: Scalars['String'];
supportUserHash?: Maybe<Scalars['String']>;
updatedAt: Scalars['DateTime'];
workspaceMember?: Maybe<WorkspaceMember>;
};
export type UserCreateNestedOneWithoutAssignedActivitiesInput = {
@ -2717,6 +2707,7 @@ export type UserOrderByWithRelationInput = {
comments?: InputMaybe<CommentOrderByRelationAggregateInput>;
companies?: InputMaybe<CompanyOrderByRelationAggregateInput>;
createdAt?: InputMaybe<SortOrder>;
defaultWorkspaceId?: InputMaybe<SortOrder>;
disabled?: InputMaybe<SortOrder>;
email?: InputMaybe<SortOrder>;
emailVerified?: InputMaybe<SortOrder>;
@ -2727,8 +2718,6 @@ export type UserOrderByWithRelationInput = {
locale?: InputMaybe<SortOrder>;
metadata?: InputMaybe<SortOrder>;
phoneNumber?: InputMaybe<SortOrder>;
settings?: InputMaybe<UserSettingsOrderByWithRelationInput>;
settingsId?: InputMaybe<SortOrder>;
updatedAt?: InputMaybe<SortOrder>;
};
@ -2741,6 +2730,7 @@ export enum UserScalarFieldEnum {
AvatarUrl = 'avatarUrl',
CanImpersonate = 'canImpersonate',
CreatedAt = 'createdAt',
DefaultWorkspaceId = 'defaultWorkspaceId',
DeletedAt = 'deletedAt',
Disabled = 'disabled',
Email = 'email',
@ -2753,7 +2743,6 @@ export enum UserScalarFieldEnum {
Metadata = 'metadata',
PasswordHash = 'passwordHash',
PhoneNumber = 'phoneNumber',
SettingsId = 'settingsId',
UpdatedAt = 'updatedAt'
}
@ -2765,7 +2754,6 @@ export type UserSettings = {
id: Scalars['ID'];
locale: Scalars['String'];
updatedAt: Scalars['DateTime'];
user?: Maybe<User>;
};
export type UserSettingsOrderByWithRelationInput = {
@ -2775,7 +2763,6 @@ export type UserSettingsOrderByWithRelationInput = {
id?: InputMaybe<SortOrder>;
locale?: InputMaybe<SortOrder>;
updatedAt?: InputMaybe<SortOrder>;
user?: InputMaybe<UserOrderByWithRelationInput>;
};
export type UserSettingsRelationFilter = {
@ -2783,24 +2770,11 @@ export type UserSettingsRelationFilter = {
isNot?: InputMaybe<UserSettingsWhereInput>;
};
export type UserSettingsUpdateOneRequiredWithoutUserNestedInput = {
update?: InputMaybe<UserSettingsUpdateWithoutUserInput>;
};
export type UserSettingsUpdateOneWithoutWorkspaceMemberNestedInput = {
connect?: InputMaybe<UserSettingsWhereUniqueInput>;
disconnect?: InputMaybe<Scalars['Boolean']>;
};
export type UserSettingsUpdateWithoutUserInput = {
WorkspaceMember?: InputMaybe<WorkspaceMemberUpdateManyWithoutSettingsNestedInput>;
colorScheme?: InputMaybe<ColorScheme>;
createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>;
locale?: InputMaybe<Scalars['String']>;
updatedAt?: InputMaybe<Scalars['DateTime']>;
};
export type UserSettingsWhereInput = {
AND?: InputMaybe<Array<UserSettingsWhereInput>>;
NOT?: InputMaybe<Array<UserSettingsWhereInput>>;
@ -2811,7 +2785,6 @@ export type UserSettingsWhereInput = {
id?: InputMaybe<StringFilter>;
locale?: InputMaybe<StringFilter>;
updatedAt?: InputMaybe<DateTimeFilter>;
user?: InputMaybe<UserRelationFilter>;
};
export type UserSettingsWhereUniqueInput = {
@ -2827,6 +2800,7 @@ export type UserUpdateInput = {
comments?: InputMaybe<CommentUpdateManyWithoutAuthorNestedInput>;
companies?: InputMaybe<CompanyUpdateManyWithoutAccountOwnerNestedInput>;
createdAt?: InputMaybe<Scalars['DateTime']>;
defaultWorkspaceId?: InputMaybe<Scalars['String']>;
disabled?: InputMaybe<Scalars['Boolean']>;
email?: InputMaybe<Scalars['String']>;
emailVerified?: InputMaybe<Scalars['Boolean']>;
@ -2837,7 +2811,6 @@ export type UserUpdateInput = {
locale?: InputMaybe<Scalars['String']>;
metadata?: InputMaybe<Scalars['JSON']>;
phoneNumber?: InputMaybe<Scalars['String']>;
settings?: InputMaybe<UserSettingsUpdateOneRequiredWithoutUserNestedInput>;
updatedAt?: InputMaybe<Scalars['DateTime']>;
};
@ -2845,10 +2818,6 @@ export type UserUpdateOneRequiredWithoutAuthoredActivitiesNestedInput = {
connect?: InputMaybe<UserWhereUniqueInput>;
};
export type UserUpdateOneRequiredWithoutWorkspaceMemberNestedInput = {
connect?: InputMaybe<UserWhereUniqueInput>;
};
export type UserUpdateOneWithoutAssignedActivitiesNestedInput = {
connect?: InputMaybe<UserWhereUniqueInput>;
disconnect?: InputMaybe<Scalars['Boolean']>;
@ -2871,6 +2840,7 @@ export type UserWhereInput = {
comments?: InputMaybe<CommentListRelationFilter>;
companies?: InputMaybe<CompanyListRelationFilter>;
createdAt?: InputMaybe<DateTimeFilter>;
defaultWorkspaceId?: InputMaybe<StringNullableFilter>;
disabled?: InputMaybe<BoolFilter>;
email?: InputMaybe<StringFilter>;
emailVerified?: InputMaybe<BoolFilter>;
@ -2881,15 +2851,12 @@ export type UserWhereInput = {
locale?: InputMaybe<StringFilter>;
metadata?: InputMaybe<JsonNullableFilter>;
phoneNumber?: InputMaybe<StringNullableFilter>;
settings?: InputMaybe<UserSettingsRelationFilter>;
settingsId?: InputMaybe<StringFilter>;
updatedAt?: InputMaybe<DateTimeFilter>;
};
export type UserWhereUniqueInput = {
email?: InputMaybe<Scalars['String']>;
id?: InputMaybe<Scalars['String']>;
settingsId?: InputMaybe<Scalars['String']>;
};
export type Verify = {
@ -2996,7 +2963,6 @@ export type WorkspaceMember = {
settings?: Maybe<UserSettings>;
settingsId?: Maybe<Scalars['String']>;
updatedAt: Scalars['DateTime'];
user: User;
userId: Scalars['String'];
workspace: Workspace;
};
@ -3040,7 +3006,6 @@ export type WorkspaceMemberOrderByWithRelationInput = {
settings?: InputMaybe<UserSettingsOrderByWithRelationInput>;
settingsId?: InputMaybe<SortOrder>;
updatedAt?: InputMaybe<SortOrder>;
user?: InputMaybe<UserOrderByWithRelationInput>;
userId?: InputMaybe<SortOrder>;
};
@ -3072,13 +3037,7 @@ export type WorkspaceMemberUpdateInput = {
id?: InputMaybe<Scalars['String']>;
settings?: InputMaybe<UserSettingsUpdateOneWithoutWorkspaceMemberNestedInput>;
updatedAt?: InputMaybe<Scalars['DateTime']>;
user?: InputMaybe<UserUpdateOneRequiredWithoutWorkspaceMemberNestedInput>;
};
export type WorkspaceMemberUpdateManyWithoutSettingsNestedInput = {
connect?: InputMaybe<Array<WorkspaceMemberWhereUniqueInput>>;
disconnect?: InputMaybe<Array<WorkspaceMemberWhereUniqueInput>>;
set?: InputMaybe<Array<WorkspaceMemberWhereUniqueInput>>;
userId?: InputMaybe<Scalars['String']>;
};
export type WorkspaceMemberUpdateManyWithoutWorkspaceNestedInput = {
@ -3118,7 +3077,6 @@ export type WorkspaceMemberWhereInput = {
settings?: InputMaybe<UserSettingsRelationFilter>;
settingsId?: InputMaybe<StringNullableFilter>;
updatedAt?: InputMaybe<DateTimeFilter>;
user?: InputMaybe<UserRelationFilter>;
userId?: InputMaybe<StringFilter>;
};
@ -3207,6 +3165,22 @@ export type ObjectEdge = {
node: Object;
};
export type RefreshTokenV2 = {
__typename?: 'refreshTokenV2';
createdAt: Scalars['DateTime'];
expiresAt: Scalars['DateTime'];
id: Scalars['ID'];
updatedAt: Scalars['DateTime'];
};
export type RefreshTokenV2Edge = {
__typename?: 'refreshTokenV2Edge';
/** Cursor for this node. */
cursor: Scalars['ConnectionCursor'];
/** The node containing the refreshTokenV2 */
node: RefreshTokenV2;
};
export type Relation = {
__typename?: 'relation';
createdAt: Scalars['DateTime'];
@ -3229,6 +3203,36 @@ export type RelationEdge = {
node: Relation;
};
export type UserV2 = {
__typename?: 'userV2';
avatarUrl: Scalars['String'];
canImpersonate: Scalars['Boolean'];
createdAt: Scalars['DateTime'];
deletedAt?: Maybe<Scalars['DateTime']>;
disabled?: Maybe<Scalars['Boolean']>;
displayName: Scalars['String'];
email: Scalars['String'];
emailVerified: Scalars['Boolean'];
firstName: Scalars['String'];
id: Scalars['ID'];
lastName: Scalars['String'];
lastSeen?: Maybe<Scalars['DateTime']>;
locale: Scalars['String'];
metadata?: Maybe<Scalars['JSONObject']>;
passwordHash?: Maybe<Scalars['String']>;
phoneNumber?: Maybe<Scalars['String']>;
supportUserHash?: Maybe<Scalars['String']>;
updatedAt: Scalars['DateTime'];
};
export type UserV2Edge = {
__typename?: 'userV2Edge';
/** Cursor for this node. */
cursor: Scalars['ConnectionCursor'];
/** The node containing the userV2 */
node: UserV2;
};
export type ActivityWithTargetsFragment = { __typename?: 'Activity', id: string, createdAt: string, updatedAt: string, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, createdAt: string, updatedAt: string, companyId?: string | null, personId?: string | null }> | null };
export type ActivityQueryFragmentFragment = { __typename?: 'Activity', id: string, createdAt: string, title?: string | null, body?: string | null, type: ActivityType, completedAt?: string | null, dueAt?: string | null, assignee?: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string, avatarUrl?: string | null } | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, companyId?: string | null, personId?: string | null, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null, person?: { __typename?: 'Person', id: string, displayName: string, avatarUrl?: string | null } | null }> | null };
@ -3329,7 +3333,7 @@ export type AuthTokenFragmentFragment = { __typename?: 'AuthToken', token: strin
export type AuthTokensFragmentFragment = { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } };
export type UserQueryFragmentFragment = { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, avatarUrl?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null }, assignedActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredAttachments?: Array<{ __typename?: 'Attachment', id: string, name: string, type: AttachmentType }> | null, settings?: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } | null, companies?: Array<{ __typename?: 'Company', id: string, name: string, domainName: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, body: string }> | null } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } };
export type UserQueryFragmentFragment = { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null };
export type ChallengeMutationVariables = Exact<{
email: Scalars['String'];
@ -3344,7 +3348,7 @@ export type ImpersonateMutationVariables = Exact<{
}>;
export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, avatarUrl?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null }, assignedActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredAttachments?: Array<{ __typename?: 'Attachment', id: string, name: string, type: AttachmentType }> | null, settings?: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } | null, companies?: Array<{ __typename?: 'Company', id: string, name: string, domainName: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, body: string }> | null } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type RenewTokenMutationVariables = Exact<{
refreshToken: Scalars['String'];
@ -3367,7 +3371,7 @@ export type VerifyMutationVariables = Exact<{
}>;
export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, avatarUrl?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null }, assignedActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredAttachments?: Array<{ __typename?: 'Attachment', id: string, name: string, type: AttachmentType }> | null, settings?: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } | null, companies?: Array<{ __typename?: 'Company', id: string, name: string, domainName: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, body: string }> | null } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type CheckUserExistsQueryVariables = Exact<{
email: Scalars['String'];
@ -3744,20 +3748,18 @@ export type UpdateUserMutationVariables = Exact<{
}>;
export type UpdateUserMutation = { __typename?: 'Mutation', updateUser: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null }, assignedActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredAttachments?: Array<{ __typename?: 'Attachment', id: string, name: string, type: AttachmentType }> | null, settings?: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } | null, companies?: Array<{ __typename?: 'Company', id: string, name: string, domainName: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, body: string }> | null } | null, settings: { __typename?: 'UserSettings', id: string, locale: string, colorScheme: ColorScheme } } };
export type UpdateUserMutation = { __typename?: 'Mutation', updateUser: { __typename?: 'User', id: string, email: string } };
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', avatarUrl?: string | null, canImpersonate: boolean, supportUserHash?: string | null, id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null }, assignedActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredAttachments?: Array<{ __typename?: 'Attachment', id: string, name: string, type: AttachmentType }> | null, settings?: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } | null, companies?: Array<{ __typename?: 'Company', id: string, name: string, domainName: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, body: string }> | null } | null, settings: { __typename?: 'UserSettings', id: string, locale: string, colorScheme: ColorScheme } } };
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', canImpersonate: boolean, supportUserHash?: string | null, id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null } };
export type GetUsersQueryVariables = Exact<{ [key: string]: never; }>;
export type GetUsersQuery = { __typename?: 'Query', findManyUser: Array<{ __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null }> };
export type WorkspaceMemberFieldsFragmentFragment = { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null }, assignedActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredAttachments?: Array<{ __typename?: 'Attachment', id: string, name: string, type: AttachmentType }> | null, settings?: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } | null, companies?: Array<{ __typename?: 'Company', id: string, name: string, domainName: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, body: string }> | null };
export type DeleteCurrentWorkspaceMutationVariables = Exact<{ [key: string]: never; }>;
@ -3768,13 +3770,6 @@ export type RemoveWorkspaceLogoMutationVariables = Exact<{ [key: string]: never;
export type RemoveWorkspaceLogoMutation = { __typename?: 'Mutation', updateWorkspace: { __typename?: 'Workspace', id: string } };
export type RemoveWorkspaceMemberMutationVariables = Exact<{
where: WorkspaceMemberWhereUniqueInput;
}>;
export type RemoveWorkspaceMemberMutation = { __typename?: 'Mutation', deleteWorkspaceMember: { __typename?: 'WorkspaceMember', id: string } };
export type UpdateWorkspaceMutationVariables = Exact<{
data: WorkspaceUpdateInput;
}>;
@ -3789,13 +3784,10 @@ export type UploadWorkspaceLogoMutationVariables = Exact<{
export type UploadWorkspaceLogoMutation = { __typename?: 'Mutation', uploadWorkspaceLogo: string };
export type UpdateOneWorkspaceMemberMutationVariables = Exact<{
data: WorkspaceMemberUpdateInput;
where: WorkspaceMemberWhereUniqueInput;
}>;
export type GetCurrentWorkspaceQueryVariables = Exact<{ [key: string]: never; }>;
export type UpdateOneWorkspaceMemberMutation = { __typename?: 'Mutation', UpdateOneWorkspaceMember: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null }, assignedActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredAttachments?: Array<{ __typename?: 'Attachment', id: string, name: string, type: AttachmentType }> | null, settings?: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } | null, companies?: Array<{ __typename?: 'Company', id: string, name: string, domainName: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, body: string }> | null } };
export type GetCurrentWorkspaceQuery = { __typename?: 'Query', currentWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null } };
export type GetWorkspaceFromInviteHashQueryVariables = Exact<{
inviteHash: Scalars['String'];
@ -3809,7 +3801,7 @@ export type GetWorkspaceMembersQueryVariables = Exact<{
}>;
export type GetWorkspaceMembersQuery = { __typename?: 'Query', workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: string, user: { __typename?: 'User', avatarUrl?: string | null, id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null } }> };
export type GetWorkspaceMembersQuery = { __typename?: 'Query', workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: string }> };
export const ActivityWithTargetsFragmentDoc = gql`
fragment ActivityWithTargets on Activity {
@ -3918,50 +3910,6 @@ export const UserQueryFragmentFragmentDoc = gql`
lastName
canImpersonate
supportUserHash
avatarUrl
workspaceMember {
id
allowImpersonation
workspace {
id
domainName
displayName
logo
inviteHash
}
assignedActivities {
id
title
}
authoredActivities {
id
title
}
authoredAttachments {
id
name
type
}
settings {
id
colorScheme
locale
}
companies {
id
name
domainName
}
comments {
id
body
}
}
settings {
id
colorScheme
locale
}
}
`;
export const BaseAccountOwnerFragmentFragmentDoc = gql`
@ -4034,46 +3982,6 @@ export const UserFieldsFragmentFragmentDoc = gql`
lastName
}
`;
export const WorkspaceMemberFieldsFragmentFragmentDoc = gql`
fragment workspaceMemberFieldsFragment on WorkspaceMember {
id
allowImpersonation
workspace {
id
domainName
displayName
logo
inviteHash
}
assignedActivities {
id
title
}
authoredActivities {
id
title
}
authoredAttachments {
id
name
type
}
settings {
id
colorScheme
locale
}
companies {
id
name
domainName
}
comments {
id
body
}
}
`;
export const AddActivityTargetsOnActivityDocument = gql`
mutation AddActivityTargetsOnActivity($activityId: String!, $activityTargetInputs: [ActivityTargetCreateManyActivityInput!]!) {
updateOneActivity(
@ -6519,52 +6427,6 @@ export const UpdateUserDocument = gql`
updateUser(data: $data, where: $where) {
id
email
displayName
firstName
lastName
avatarUrl
workspaceMember {
id
workspace {
id
domainName
displayName
logo
inviteHash
}
assignedActivities {
id
title
}
authoredActivities {
id
title
}
authoredAttachments {
id
name
type
}
settings {
id
colorScheme
locale
}
companies {
id
name
domainName
}
comments {
id
body
}
}
settings {
id
locale
colorScheme
}
}
}
`;
@ -6599,21 +6461,11 @@ export const GetCurrentUserDocument = gql`
query GetCurrentUser {
currentUser {
...userFieldsFragment
avatarUrl
canImpersonate
workspaceMember {
...workspaceMemberFieldsFragment
}
settings {
id
locale
colorScheme
}
supportUserHash
}
}
${UserFieldsFragmentFragmentDoc}
${WorkspaceMemberFieldsFragmentFragmentDoc}`;
${UserFieldsFragmentFragmentDoc}`;
/**
* __useGetCurrentUserQuery__
@ -6739,39 +6591,6 @@ export function useRemoveWorkspaceLogoMutation(baseOptions?: Apollo.MutationHook
export type RemoveWorkspaceLogoMutationHookResult = ReturnType<typeof useRemoveWorkspaceLogoMutation>;
export type RemoveWorkspaceLogoMutationResult = Apollo.MutationResult<RemoveWorkspaceLogoMutation>;
export type RemoveWorkspaceLogoMutationOptions = Apollo.BaseMutationOptions<RemoveWorkspaceLogoMutation, RemoveWorkspaceLogoMutationVariables>;
export const RemoveWorkspaceMemberDocument = gql`
mutation RemoveWorkspaceMember($where: WorkspaceMemberWhereUniqueInput!) {
deleteWorkspaceMember(where: $where) {
id
}
}
`;
export type RemoveWorkspaceMemberMutationFn = Apollo.MutationFunction<RemoveWorkspaceMemberMutation, RemoveWorkspaceMemberMutationVariables>;
/**
* __useRemoveWorkspaceMemberMutation__
*
* To run a mutation, you first call `useRemoveWorkspaceMemberMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useRemoveWorkspaceMemberMutation` 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 [removeWorkspaceMemberMutation, { data, loading, error }] = useRemoveWorkspaceMemberMutation({
* variables: {
* where: // value for 'where'
* },
* });
*/
export function useRemoveWorkspaceMemberMutation(baseOptions?: Apollo.MutationHookOptions<RemoveWorkspaceMemberMutation, RemoveWorkspaceMemberMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<RemoveWorkspaceMemberMutation, RemoveWorkspaceMemberMutationVariables>(RemoveWorkspaceMemberDocument, options);
}
export type RemoveWorkspaceMemberMutationHookResult = ReturnType<typeof useRemoveWorkspaceMemberMutation>;
export type RemoveWorkspaceMemberMutationResult = Apollo.MutationResult<RemoveWorkspaceMemberMutation>;
export type RemoveWorkspaceMemberMutationOptions = Apollo.BaseMutationOptions<RemoveWorkspaceMemberMutation, RemoveWorkspaceMemberMutationVariables>;
export const UpdateWorkspaceDocument = gql`
mutation UpdateWorkspace($data: WorkspaceUpdateInput!) {
updateWorkspace(data: $data) {
@ -6839,40 +6658,42 @@ export function useUploadWorkspaceLogoMutation(baseOptions?: Apollo.MutationHook
export type UploadWorkspaceLogoMutationHookResult = ReturnType<typeof useUploadWorkspaceLogoMutation>;
export type UploadWorkspaceLogoMutationResult = Apollo.MutationResult<UploadWorkspaceLogoMutation>;
export type UploadWorkspaceLogoMutationOptions = Apollo.BaseMutationOptions<UploadWorkspaceLogoMutation, UploadWorkspaceLogoMutationVariables>;
export const UpdateOneWorkspaceMemberDocument = gql`
mutation UpdateOneWorkspaceMember($data: WorkspaceMemberUpdateInput!, $where: WorkspaceMemberWhereUniqueInput!) {
UpdateOneWorkspaceMember(data: $data, where: $where) {
...workspaceMemberFieldsFragment
export const GetCurrentWorkspaceDocument = gql`
query getCurrentWorkspace {
currentWorkspace {
id
displayName
logo
}
}
${WorkspaceMemberFieldsFragmentFragmentDoc}`;
export type UpdateOneWorkspaceMemberMutationFn = Apollo.MutationFunction<UpdateOneWorkspaceMemberMutation, UpdateOneWorkspaceMemberMutationVariables>;
`;
/**
* __useUpdateOneWorkspaceMemberMutation__
* __useGetCurrentWorkspaceQuery__
*
* To run a mutation, you first call `useUpdateOneWorkspaceMemberMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateOneWorkspaceMemberMutation` 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
* To run a query within a React component, call `useGetCurrentWorkspaceQuery` and pass it any options that fit your needs.
* When your component renders, `useGetCurrentWorkspaceQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @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;
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const [updateOneWorkspaceMemberMutation, { data, loading, error }] = useUpdateOneWorkspaceMemberMutation({
* const { data, loading, error } = useGetCurrentWorkspaceQuery({
* variables: {
* data: // value for 'data'
* where: // value for 'where'
* },
* });
*/
export function useUpdateOneWorkspaceMemberMutation(baseOptions?: Apollo.MutationHookOptions<UpdateOneWorkspaceMemberMutation, UpdateOneWorkspaceMemberMutationVariables>) {
export function useGetCurrentWorkspaceQuery(baseOptions?: Apollo.QueryHookOptions<GetCurrentWorkspaceQuery, GetCurrentWorkspaceQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<UpdateOneWorkspaceMemberMutation, UpdateOneWorkspaceMemberMutationVariables>(UpdateOneWorkspaceMemberDocument, options);
return Apollo.useQuery<GetCurrentWorkspaceQuery, GetCurrentWorkspaceQueryVariables>(GetCurrentWorkspaceDocument, options);
}
export type UpdateOneWorkspaceMemberMutationHookResult = ReturnType<typeof useUpdateOneWorkspaceMemberMutation>;
export type UpdateOneWorkspaceMemberMutationResult = Apollo.MutationResult<UpdateOneWorkspaceMemberMutation>;
export type UpdateOneWorkspaceMemberMutationOptions = Apollo.BaseMutationOptions<UpdateOneWorkspaceMemberMutation, UpdateOneWorkspaceMemberMutationVariables>;
export function useGetCurrentWorkspaceLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetCurrentWorkspaceQuery, GetCurrentWorkspaceQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetCurrentWorkspaceQuery, GetCurrentWorkspaceQueryVariables>(GetCurrentWorkspaceDocument, options);
}
export type GetCurrentWorkspaceQueryHookResult = ReturnType<typeof useGetCurrentWorkspaceQuery>;
export type GetCurrentWorkspaceLazyQueryHookResult = ReturnType<typeof useGetCurrentWorkspaceLazyQuery>;
export type GetCurrentWorkspaceQueryResult = Apollo.QueryResult<GetCurrentWorkspaceQuery, GetCurrentWorkspaceQueryVariables>;
export const GetWorkspaceFromInviteHashDocument = gql`
query GetWorkspaceFromInviteHash($inviteHash: String!) {
findWorkspaceFromInviteHash(inviteHash: $inviteHash) {
@ -6914,13 +6735,9 @@ export const GetWorkspaceMembersDocument = gql`
query GetWorkspaceMembers($where: WorkspaceMemberWhereInput) {
workspaceMembers: findManyWorkspaceMember(where: $where) {
id
user {
...userFieldsFragment
avatarUrl
}
}
}
${UserFieldsFragmentFragmentDoc}`;
`;
/**
* __useGetWorkspaceMembersQuery__

View File

@ -3,6 +3,7 @@ import { useRecoilState, useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { GET_COMPANIES } from '@/companies/graphql/queries/getCompanies';
import { GET_PEOPLE } from '@/people/graphql/queries/getPeople';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
@ -23,6 +24,7 @@ export const useOpenCreateActivityDrawer = () => {
const { openRightDrawer } = useRightDrawer();
const [createActivityMutation] = useCreateActivityMutation();
const currentUser = useRecoilValue(currentUserState);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const setHotkeyScope = useSetHotkeyScope();
const [, setActivityTargetableEntityArray] = useRecoilState(
@ -49,11 +51,11 @@ export const useOpenCreateActivityDrawer = () => {
updatedAt: now,
author: { connect: { id: currentUser?.id ?? '' } },
workspaceMemberAuthor: {
connect: { id: currentUser?.workspaceMember?.id ?? '' },
connect: { id: currentWorkspaceMember?.id ?? '' },
},
assignee: { connect: { id: assigneeId ?? currentUser?.id ?? '' } },
workspaceMemberAssignee: {
connect: { id: currentUser?.workspaceMember?.id ?? '' },
connect: { id: currentWorkspaceMember?.id ?? '' },
},
type: type,
activityTargets: {

View File

@ -1,7 +1,8 @@
import { DateTime } from 'luxon';
import { useRecoilState } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { turnFilterIntoWhereClause } from '@/ui/object/object-filter-dropdown/utils/turnFilterIntoWhereClause';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { ActivityType, useGetActivitiesQuery } from '~/generated/graphql';
@ -9,6 +10,7 @@ import { parseDate } from '~/utils/date-utils';
export const useCurrentUserTaskCount = () => {
const [currentUser] = useRecoilState(currentUserState);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { data } = useGetActivitiesQuery({
variables: {
@ -20,8 +22,11 @@ export const useCurrentUserTaskCount = () => {
fieldMetadataId: 'assigneeId',
value: currentUser.id,
operand: ViewFilterOperand.Is,
displayValue: currentUser.displayName,
displayAvatarUrl: currentUser.avatarUrl ?? undefined,
displayValue:
currentWorkspaceMember?.firstName +
' ' +
currentWorkspaceMember?.lastName,
displayAvatarUrl: currentWorkspaceMember?.avatarUrl ?? undefined,
definition: {
type: 'ENTITY',
},

View File

@ -9,7 +9,7 @@ import { useRecoilCallback } from 'recoil';
import { GET_COMPANIES } from '@/companies/graphql/queries/getCompanies';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { generateFindManyCustomObjectsQuery } from '@/object-record/utils/generateFindManyCustomObjectsQuery';
import { useGenerateFindManyCustomObjectsQuery } from '@/object-record/utils/useGenerateFindManyCustomObjectsQuery';
import { GET_PEOPLE } from '@/people/graphql/queries/getPeople';
import { GET_API_KEYS } from '@/settings/developers/graphql/queries/getApiKeys';
import {
@ -54,7 +54,7 @@ export const useOptimisticEffect = () => {
objectMetadataItem?: ObjectMetadataItem;
}) => {
if (isUsingFlexibleBackend && objectMetadataItem) {
const generatedQuery = generateFindManyCustomObjectsQuery({
const generatedQuery = useGenerateFindManyCustomObjectsQuery({
objectMetadataItem,
});

View File

@ -9,49 +9,5 @@ export const USER_QUERY_FRAGMENT = gql`
lastName
canImpersonate
supportUserHash
avatarUrl
workspaceMember {
id
allowImpersonation
workspace {
id
domainName
displayName
logo
inviteHash
}
assignedActivities {
id
title
}
authoredActivities {
id
title
}
authoredAttachments {
id
name
type
}
settings {
id
colorScheme
locale
}
companies {
id
name
domainName
}
comments {
id
body
}
}
settings {
id
colorScheme
locale
}
}
`;

View File

@ -4,12 +4,19 @@ import {
snapshot_UNSTABLE,
useGotoRecoilSnapshot,
useRecoilState,
useSetRecoilState,
} from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { isVerifyPendingState } from '@/auth/states/isVerifyPendingState';
import { CREATE_ONE_WORKSPACE_MEMBER_V2 } from '@/object-record/graphql/mutation/createOneWorkspaceMember';
import { FIND_ONE_WORKSPACE_MEMBER_V2 } from '@/object-record/graphql/queries/findOneWorkspaceMember';
import { REACT_APP_SERVER_AUTH_URL } from '~/config';
import {
useChallengeMutation,
useCheckUserExistsLazyQuery,
useGetCurrentWorkspaceLazyQuery,
useSignUpMutation,
useVerifyMutation,
} from '~/generated/graphql';
@ -19,13 +26,20 @@ import { tokenPairState } from '../states/tokenPairState';
export const useAuth = () => {
const [, setTokenPair] = useRecoilState(tokenPairState);
const [, setCurrentUser] = useRecoilState(currentUserState);
const setCurrentUser = useSetRecoilState(currentUserState);
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const setIsVerifyPendingState = useSetRecoilState(isVerifyPendingState);
const [challenge] = useChallengeMutation();
const [signUp] = useSignUpMutation();
const [verify] = useVerifyMutation();
const [checkUserExistsQuery, { data: checkUserExistsData }] =
useCheckUserExistsLazyQuery();
const [getCurrentWorkspaceQuery, { data: getCurrentWorkspaceData }] =
useGetCurrentWorkspaceLazyQuery();
const client = useApolloClient();
@ -67,36 +81,56 @@ export const useAuth = () => {
throw new Error('No verify result');
}
if (!verifyResult.data?.verify.user.workspaceMember) {
throw new Error('No workspace member');
}
if (!verifyResult.data?.verify.user.workspaceMember.settings) {
throw new Error('No settings');
}
setCurrentUser({
...verifyResult.data?.verify.user,
workspaceMember: {
...verifyResult.data?.verify.user.workspaceMember,
settings: verifyResult.data?.verify.user.workspaceMember.settings,
setTokenPair(verifyResult.data?.verify.tokens);
const workspaceMember = await client.query({
query: FIND_ONE_WORKSPACE_MEMBER_V2,
variables: {
filter: {
userId: { eq: verifyResult.data?.verify.user.id },
},
},
});
setTokenPair(verifyResult.data?.verify.tokens);
const currentWorkspace = await getCurrentWorkspaceQuery();
return verifyResult.data?.verify;
setCurrentUser(verifyResult.data?.verify.user);
setCurrentWorkspaceMember(workspaceMember.data?.findMany);
setCurrentWorkspace(currentWorkspace.data?.currentWorkspace ?? null);
return {
user: verifyResult.data?.verify.user,
workspaceMember: workspaceMember.data?.findMany,
workspace: currentWorkspace.data?.currentWorkspace,
tokens: verifyResult.data?.verify.tokens,
};
},
[setTokenPair, verify, setCurrentUser],
[
verify,
setTokenPair,
client,
getCurrentWorkspaceQuery,
setCurrentUser,
setCurrentWorkspaceMember,
setCurrentWorkspace,
],
);
const handleCrendentialsSignIn = useCallback(
async (email: string, password: string) => {
const { loginToken } = await handleChallenge(email, password);
setIsVerifyPendingState(true);
const { user } = await handleVerify(loginToken.token);
return user;
const { user, workspaceMember, workspace } = await handleVerify(
loginToken.token,
);
setIsVerifyPendingState(false);
return {
user,
workspaceMember,
workspace,
};
},
[handleChallenge, handleVerify],
[handleChallenge, handleVerify, setIsVerifyPendingState],
);
const handleSignOut = useCallback(() => {
@ -110,6 +144,8 @@ export const useAuth = () => {
const handleCredentialsSignUp = useCallback(
async (email: string, password: string, workspaceInviteHash?: string) => {
setIsVerifyPendingState(true);
const signUpResult = await signUp({
variables: {
email,
@ -126,13 +162,30 @@ export const useAuth = () => {
throw new Error('No login token');
}
const { user } = await handleVerify(
const { user, workspace } = await handleVerify(
signUpResult.data?.signUp.loginToken.token,
);
return user;
const workspaceMember = await client.mutate({
mutation: CREATE_ONE_WORKSPACE_MEMBER_V2,
variables: {
input: {
firstName: user.firstName ?? '',
lastName: user.lastName ?? '',
colorScheme: 'Light',
userId: user.id,
allowImpersonation: true,
locale: 'en',
},
},
});
setCurrentWorkspaceMember(workspaceMember.data?.createWorkspaceMemberV2);
setIsVerifyPendingState(false);
return { user, workspaceMember, workspace };
},
[signUp, handleVerify],
[setIsVerifyPendingState, signUp, handleVerify, client],
);
const handleGoogleLogin = useCallback((workspaceInviteHash?: string) => {

View File

@ -1,9 +1,12 @@
import { useRecoilState } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';
import { isVerifyPendingState } from '@/auth/states/isVerifyPendingState';
import { tokenPairState } from '../states/tokenPairState';
export const useIsLogged = (): boolean => {
const [tokenPair] = useRecoilState(tokenPairState);
const isVerifyPending = useRecoilValue(isVerifyPendingState);
return !!tokenPair;
return !!tokenPair && !isVerifyPending;
};

View File

@ -1,15 +1,26 @@
import { useRecoilState } from 'recoil';
import { useRecoilValue } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useIsLogged } from '../hooks/useIsLogged';
import { currentUserState } from '../states/currentUserState';
import {
getOnboardingStatus,
OnboardingStatus,
} from '../utils/getOnboardingStatus';
export const useOnboardingStatus = (): OnboardingStatus | undefined => {
const [currentUser] = useRecoilState(currentUserState);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const isLoggedIn = useIsLogged();
return getOnboardingStatus(isLoggedIn, currentUser);
console.log(
getOnboardingStatus(isLoggedIn, currentWorkspaceMember, currentWorkspace),
);
return getOnboardingStatus(
isLoggedIn,
currentWorkspaceMember,
currentWorkspace,
);
};

View File

@ -62,7 +62,7 @@ export const useSignInUp = () => {
});
const [showErrors, setShowErrors] = useState(false);
const { data: workspace } = useGetWorkspaceFromInviteHashQuery({
const { data: workspaceFromInviteHash } = useGetWorkspaceFromInviteHashQuery({
variables: { inviteHash: workspaceInviteHash || '' },
});
@ -119,20 +119,23 @@ export const useSignInUp = () => {
if (!data.email || !data.password) {
throw new Error('Email and password are required');
}
let user;
let currentWorkspace;
if (signInUpMode === SignInUpMode.SignIn) {
user = await signInWithCredentials(
const { workspace } = await signInWithCredentials(
data.email.toLowerCase(),
data.password,
);
currentWorkspace = workspace;
} else {
user = await signUpWithCredentials(
const { workspace } = await signUpWithCredentials(
data.email.toLowerCase(),
data.password,
workspaceInviteHash,
);
currentWorkspace = workspace;
}
if (user?.workspaceMember?.workspace?.displayName) {
if (currentWorkspace?.displayName) {
navigate('/');
} else {
navigate('/create/workspace');
@ -144,12 +147,12 @@ export const useSignInUp = () => {
}
},
[
navigate,
signInUpMode,
signInWithCredentials,
signUpWithCredentials,
workspaceInviteHash,
navigate,
enqueueSnackBar,
signInUpMode,
],
);
@ -188,6 +191,6 @@ export const useSignInUp = () => {
goBackToEmailStep,
submitCredentials,
form,
workspace: workspace?.findWorkspaceFromInviteHash,
workspace: workspaceFromInviteHash?.findWorkspaceFromInviteHash,
};
};

View File

@ -1,32 +1,11 @@
import { atom } from 'recoil';
import {
User,
UserSettings,
Workspace,
WorkspaceMember,
} from '~/generated/graphql';
import { User } from '~/generated/graphql';
export type CurrentUser = Pick<
User,
| 'id'
| 'email'
| 'displayName'
| 'firstName'
| 'lastName'
| 'avatarUrl'
| 'canImpersonate'
| 'supportUserHash'
> & {
workspaceMember: Pick<WorkspaceMember, 'id' | 'allowImpersonation'> & {
workspace: Pick<
Workspace,
'id' | 'displayName' | 'domainName' | 'inviteHash' | 'logo'
>;
settings: Pick<UserSettings, 'id' | 'colorScheme' | 'locale'>;
};
settings: Pick<UserSettings, 'id' | 'colorScheme' | 'locale'>;
};
'id' | 'email' | 'supportUserHash' | 'canImpersonate'
>;
export const currentUserState = atom<CurrentUser | null>({
key: 'currentUserState',

View File

@ -0,0 +1,18 @@
import { atom } from 'recoil';
import { ColorScheme } from '~/generated-metadata/graphql';
export type CurrentWorkspaceMember = {
id: string;
locale: string;
colorScheme: ColorScheme;
allowImpersonation: boolean;
firstName: string;
lastName: string;
avatarUrl: string;
};
export const currentWorkspaceMemberState = atom<CurrentWorkspaceMember | null>({
key: 'currentWorkspaceMemberState',
default: null,
});

View File

@ -0,0 +1,13 @@
import { atom } from 'recoil';
import { Workspace } from '~/generated-metadata/graphql';
export type CurrentWorkspace = Pick<
Workspace,
'id' | 'inviteHash' | 'logo' | 'displayName'
>;
export const currentWorkspaceState = atom<CurrentWorkspace | null>({
key: 'currentWorkspaceState',
default: null,
});

View File

@ -0,0 +1,6 @@
import { atom } from 'recoil';
export const isVerifyPendingState = atom<boolean>({
key: 'isVerifyPendingState',
default: false,
});

View File

@ -1,4 +1,5 @@
import { CurrentUser } from '../states/currentUserState';
import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState';
export enum OnboardingStatus {
OngoingUserCreation = 'ongoing_user_creation',
@ -9,21 +10,22 @@ export enum OnboardingStatus {
export const getOnboardingStatus = (
isLoggedIn: boolean,
currentUser: CurrentUser | null,
currentWorkspaceMember: CurrentWorkspaceMember | null,
currentWorkspace: CurrentWorkspace | null,
) => {
if (!isLoggedIn) {
return OnboardingStatus.OngoingUserCreation;
}
// if the user has not been fetched yet, we can't know the onboarding status
if (!currentUser) {
if (!currentWorkspaceMember) {
return undefined;
}
if (!currentUser.workspaceMember?.workspace.displayName) {
if (!currentWorkspace?.displayName) {
return OnboardingStatus.OngoingWorkspaceCreation;
}
if (!currentUser.firstName || !currentUser.lastName) {
if (!currentWorkspaceMember.firstName || !currentWorkspaceMember.lastName) {
return OnboardingStatus.OngoingProfileCreation;
}

View File

@ -27,7 +27,6 @@ export const FIND_MANY_METADATA_OBJECTS = gql`
label
description
icon
placeholder
isCustom
isActive
isNullable
@ -36,10 +35,34 @@ export const FIND_MANY_METADATA_OBJECTS = gql`
fromRelationMetadata {
id
relationType
toObjectMetadata {
id
dataSourceId
nameSingular
namePlural
}
fromObjectMetadata {
id
dataSourceId
nameSingular
namePlural
}
}
toRelationMetadata {
id
relationType
toObjectMetadata {
id
dataSourceId
nameSingular
namePlural
}
fromObjectMetadata {
id
dataSourceId
nameSingular
namePlural
}
}
}
}

View File

@ -10,7 +10,7 @@ import {
import { logError } from '~/utils/logError';
import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries';
import { formatPagedObjectMetadataItemsToObjectMetadataItems } from '../utils/formatPagedObjectMetadataItemsToObjectMetadataItems';
import { mapPaginatedObjectMetadataItemsToObjectMetadataItems } from '../utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems';
import { useApolloMetadataClient } from './useApolloMetadataClient';
@ -59,7 +59,7 @@ export const useFindManyObjectMetadataItems = ({
});
const objectMetadataItems = useMemo(() => {
return formatPagedObjectMetadataItemsToObjectMetadataItems({
return mapPaginatedObjectMetadataItemsToObjectMetadataItems({
pagedObjectMetadataItems: data,
});
}, [data]);

View File

@ -0,0 +1,74 @@
import { useMemo } from 'react';
import { useQuery } from '@apollo/client';
import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar';
import {
ObjectFilter,
ObjectMetadataItemsQuery,
ObjectMetadataItemsQueryVariables,
} from '~/generated-metadata/graphql';
import { logError } from '~/utils/logError';
import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries';
import { mapPaginatedObjectMetadataItemsToObjectMetadataItems } from '../utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems';
import { useApolloMetadataClient } from './useApolloMetadataClient';
// TODO: test fetchMore
export const useFindManyObjectMetadataItems = ({
skip,
filter,
}: { skip?: boolean; filter?: ObjectFilter } = {}) => {
const apolloMetadataClient = useApolloMetadataClient();
const { enqueueSnackBar } = useSnackBar();
const {
data,
fetchMore: fetchMoreInternal,
loading,
error,
} = useQuery<ObjectMetadataItemsQuery, ObjectMetadataItemsQueryVariables>(
FIND_MANY_METADATA_OBJECTS,
{
variables: {
filter,
},
client: apolloMetadataClient ?? undefined,
skip: skip || !apolloMetadataClient,
onError: (error) => {
logError('useFindManyObjectMetadataItems error : ' + error);
enqueueSnackBar(
`Error during useFindManyObjectMetadataItems, ${error.message}`,
{
variant: 'error',
},
);
},
onCompleted: () => {},
},
);
const hasMore = data?.objects?.pageInfo?.hasNextPage;
const fetchMore = () =>
fetchMoreInternal({
variables: {
afterCursor: data?.objects?.pageInfo?.endCursor,
},
});
const objectMetadataItems = useMemo(() => {
return mapPaginatedObjectMetadataItemsToObjectMetadataItems({
pagedObjectMetadataItems: data,
});
}, [data]);
return {
objectMetadataItems,
hasMore,
fetchMore,
loading,
error,
};
};

View File

@ -1,10 +1,10 @@
import { gql } from '@apollo/client';
import { generateCreateOneObjectMutation } from '@/object-record/utils/generateCreateOneObjectMutation';
import { generateDeleteOneObjectMutation } from '@/object-record/utils/generateDeleteOneObjectMutation';
import { generateFindManyCustomObjectsQuery } from '@/object-record/utils/generateFindManyCustomObjectsQuery';
import { generateFindOneCustomObjectQuery } from '@/object-record/utils/generateFindOneCustomObjectQuery';
import { generateUpdateOneObjectMutation } from '@/object-record/utils/generateUpdateOneObjectMutation';
import { useGenerateCreateOneObjectMutation } from '@/object-record/utils/generateCreateOneObjectMutation';
import { useGenerateDeleteOneObjectMutation } from '@/object-record/utils/useGenerateDeleteOneObjectMutation';
import { useGenerateFindManyCustomObjectsQuery } from '@/object-record/utils/useGenerateFindManyCustomObjectsQuery';
import { useGenerateFindOneCustomObjectQuery } from '@/object-record/utils/useGenerateFindOneCustomObjectQuery';
import { useGenerateUpdateOneObjectMutation } from '@/object-record/utils/useGenerateUpdateOneObjectMutation';
import { useLazyLoadIcons } from '@/ui/input/hooks/useLazyLoadIcons';
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
@ -16,13 +16,13 @@ import { formatFieldMetadataItemsAsSortDefinitions } from '../utils/formatFieldM
import { useFindManyObjectMetadataItems } from './useFindManyObjectMetadataItems';
const EMPTY_QUERY = gql`
export const EMPTY_QUERY = gql`
query EmptyQuery {
empty
}
`;
const EMPTY_MUTATION = gql`
export const EMPTY_MUTATION = gql`
mutation EmptyMutation {
empty
}
@ -74,35 +74,25 @@ export const useFindOneObjectMetadataItem = ({
icons,
});
const findManyQuery = foundObjectMetadataItem
? generateFindManyCustomObjectsQuery({
objectMetadataItem: foundObjectMetadataItem,
})
: EMPTY_QUERY;
const findManyQuery = useGenerateFindManyCustomObjectsQuery({
objectMetadataItem: foundObjectMetadataItem,
});
const findOneQuery = foundObjectMetadataItem
? generateFindOneCustomObjectQuery({
objectMetadataItem: foundObjectMetadataItem,
})
: EMPTY_QUERY;
const findOneQuery = useGenerateFindOneCustomObjectQuery({
objectMetadataItem: foundObjectMetadataItem,
});
const createOneMutation = foundObjectMetadataItem
? generateCreateOneObjectMutation({
objectMetadataItem: foundObjectMetadataItem,
})
: EMPTY_MUTATION;
const createOneMutation = useGenerateCreateOneObjectMutation({
objectMetadataItem: foundObjectMetadataItem,
});
const updateOneMutation = foundObjectMetadataItem
? generateUpdateOneObjectMutation({
objectMetadataItem: foundObjectMetadataItem,
})
: EMPTY_MUTATION;
const updateOneMutation = useGenerateUpdateOneObjectMutation({
objectMetadataItem: foundObjectMetadataItem,
});
const deleteOneMutation = foundObjectMetadataItem
? generateDeleteOneObjectMutation({
objectMetadataItem: foundObjectMetadataItem,
})
: EMPTY_MUTATION;
const deleteOneMutation = useGenerateDeleteOneObjectMutation({
objectMetadataItem: foundObjectMetadataItem,
});
return {
foundObjectMetadataItem,

View File

@ -0,0 +1,86 @@
import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems';
import { FieldType } from '@/ui/object/field/types/FieldType';
import { FieldMetadataItem } from '../types/FieldMetadataItem';
export const useMapFieldMetadataToGraphQLQuery = () => {
const { objectMetadataItems } = useFindManyObjectMetadataItems();
const mapFieldMetadataToGraphQLQuery = (field: FieldMetadataItem): any => {
// TODO: parse
const fieldType = field.type as FieldType;
const fieldIsSimpleValue = (
[
'TEXT',
'PHONE',
'DATE',
'EMAIL',
'NUMBER',
'BOOLEAN',
'DATE',
] as FieldType[]
).includes(fieldType);
const fieldIsURL = fieldType === 'URL' || fieldType === 'URL_V2';
const fieldIsMoneyAmount =
fieldType === 'MONEY' || fieldType === 'MONEY_AMOUNT_V2';
if (fieldIsSimpleValue) {
return field.name;
} else if (
fieldType === 'RELATION' &&
field.toRelationMetadata?.relationType === 'ONE_TO_MANY'
) {
console.log({ objectMetadataItems, field });
const relationMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id ===
(field.toRelationMetadata as any)?.fromObjectMetadata?.id,
);
console.log({ relationMetadataItem });
return `${field.name}
{
id
${relationMetadataItem?.fields
.filter((field) => field.type !== 'RELATION')
.map((field) => field.name)
.join('\n')}
}`;
} else if (
fieldType === 'RELATION' &&
field.fromRelationMetadata?.relationType === 'ONE_TO_MANY'
) {
return `${field.name}
{
edges {
node {
id
}
}
}`;
} else if (fieldIsURL) {
return `
${field.name}
{
text
link
}
`;
} else if (fieldIsMoneyAmount) {
return `
${field.name}
{
amount
currency
}
`;
}
};
return mapFieldMetadataToGraphQLQuery;
};

View File

@ -1,65 +0,0 @@
import { FieldType } from '@/ui/object/field/types/FieldType';
import { FieldMetadataItem } from '../types/FieldMetadataItem';
export const mapFieldMetadataToGraphQLQuery = (field: FieldMetadataItem) => {
// TODO: parse
const fieldType = field.type as FieldType;
const fieldIsSimpleValue = (
[
'TEXT',
'PHONE',
'DATE',
'EMAIL',
'NUMBER',
'BOOLEAN',
'DATE',
] as FieldType[]
).includes(fieldType);
const fieldIsURL = fieldType === 'URL' || fieldType === 'URL_V2';
const fieldIsMoneyAmount =
fieldType === 'MONEY' || fieldType === 'MONEY_AMOUNT_V2';
if (fieldIsSimpleValue) {
return field.name;
} else if (
fieldType === 'RELATION' &&
field.toRelationMetadata?.relationType === 'ONE_TO_MANY'
) {
return `${field.name}
{
id
}`;
} else if (
fieldType === 'RELATION' &&
field.fromRelationMetadata?.relationType === 'ONE_TO_MANY'
) {
return `${field.name}
{
edges {
node {
id
}
}
}`;
} else if (fieldIsURL) {
return `
${field.name}
{
text
link
}
`;
} else if (fieldIsMoneyAmount) {
return `
${field.name}
{
amount
currency
}
`;
}
};

View File

@ -2,8 +2,8 @@ import { ObjectMetadataItemsQuery } from '~/generated-metadata/graphql';
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
export const formatPagedObjectMetadataItemsToObjectMetadataItems = ({
pagedObjectMetadataItems: pagedObjectMetadataItems,
export const mapPaginatedObjectMetadataItemsToObjectMetadataItems = ({
pagedObjectMetadataItems,
}: {
pagedObjectMetadataItems: ObjectMetadataItemsQuery | undefined;
}) => {

View File

@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
export const CREATE_ONE_WORKSPACE_MEMBER_V2 = gql`
mutation CreateOneWorkspaceMemberV2($input: WorkspaceMemberV2CreateInput!) {
createWorkspaceMemberV2(data: $input) {
id
firstName
lastName
}
}
`;

View File

@ -0,0 +1,15 @@
import { gql } from '@apollo/client';
export const FIND_ONE_WORKSPACE_MEMBER_V2 = gql`
query FindManyWorkspaceMembersV2($filter: WorkspaceMemberV2FilterInput) {
workspaceMembersV2(filter: $filter) {
edges {
node {
id
firstName
lastName
}
}
}
}
`;

View File

@ -20,12 +20,10 @@ export const useDeleteOneObjectRecord = ({
const [mutate] = useMutation(deleteOneMutation);
const deleteOneObject = foundObjectMetadataItem
? (input: Record<string, any>) => {
? (idToDelete: string) => {
return mutate({
variables: {
input: {
...input,
},
idToDelete,
},
refetchQueries: [getOperationName(findManyQuery) ?? ''],
});

View File

@ -18,7 +18,7 @@ import {
PaginatedObjectTypeEdge,
PaginatedObjectTypeResults,
} from '../types/PaginatedObjectTypeResults';
import { formatPagedObjectsToObjects } from '../utils/formatPagedObjectsToObjects';
import { mapPaginatedObjectsToObjects } from '../utils/mapPaginatedObjectsToObjects';
// TODO: test with a wrong name
// TODO: add zod to validate that we have at least id on each object
@ -163,7 +163,7 @@ export const useFindManyObjectRecords = <
const objects = useMemo(
() =>
objectNamePlural
? formatPagedObjectsToObjects({
? mapPaginatedObjectsToObjects({
pagedObjects: data,
objectNamePlural,
})

View File

@ -16,6 +16,8 @@ export const useUpdateOneObjectRecord = ({
objectNameSingular,
});
console.log('update one object');
// TODO: type this with a minimal type at least with Record<string, any>
const [mutate] = useMutation(updateOneMutation);

View File

@ -1,14 +1,21 @@
import { gql } from '@apollo/client';
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery';
import { capitalize } from '~/utils/string/capitalize';
export const generateCreateOneObjectMutation = ({
export const useGenerateCreateOneObjectMutation = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItem: ObjectMetadataItem | undefined | null;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
if (!objectMetadataItem) {
return EMPTY_MUTATION;
}
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
return gql`

View File

@ -1,4 +1,4 @@
export const formatPagedObjectsToObjects = <
export const mapPaginatedObjectsToObjects = <
ObjectType extends { id: string } & Record<string, any>,
ObjectTypeQuery extends {
[objectNamePlural: string]: {

View File

@ -1,14 +1,19 @@
import { gql } from '@apollo/client';
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { capitalize } from '~/utils/string/capitalize';
export const generateDeleteOneObjectMutation = ({
export const useGenerateDeleteOneObjectMutation = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItem: ObjectMetadataItem | undefined | null;
}) => {
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
if (!objectMetadataItem) {
return EMPTY_MUTATION;
}
const capitalizedObjectName = capitalize(objectMetadataItem?.nameSingular);
return gql`
mutation DeleteOne${capitalizedObjectName}($idToDelete: ID!) {

View File

@ -1,14 +1,21 @@
import { gql } from '@apollo/client';
import { EMPTY_QUERY } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery';
import { capitalize } from '~/utils/string/capitalize';
export const generateFindManyCustomObjectsQuery = ({
export const useGenerateFindManyCustomObjectsQuery = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItem: ObjectMetadataItem | undefined | null;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
if (!objectMetadataItem) {
return EMPTY_QUERY;
}
return gql`
query FindMany${capitalize(
objectMetadataItem.namePlural,

View File

@ -1,13 +1,20 @@
import { gql } from '@apollo/client';
import { EMPTY_QUERY } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery';
export const generateFindOneCustomObjectQuery = ({
export const useGenerateFindOneCustomObjectQuery = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItem: ObjectMetadataItem | null | undefined;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
if (!objectMetadataItem) {
return EMPTY_QUERY;
}
return gql`
query FindOne${objectMetadataItem.nameSingular}($objectMetadataId: UUID!) {
${objectMetadataItem.nameSingular}(filter: {

View File

@ -1,7 +1,8 @@
import { gql } from '@apollo/client';
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery';
import { capitalize } from '~/utils/string/capitalize';
export const getUpdateOneObjectMutationGraphQLField = ({
@ -12,11 +13,17 @@ export const getUpdateOneObjectMutationGraphQLField = ({
return `update${capitalize(objectNameSingular)}`;
};
export const generateUpdateOneObjectMutation = ({
export const useGenerateUpdateOneObjectMutation = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItem: ObjectMetadataItem | undefined | null;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
if (!objectMetadataItem) {
return EMPTY_MUTATION;
}
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
const graphQLFieldForUpdateOneObjectMutation =

View File

@ -0,0 +1,149 @@
import { QueryHookOptions, QueryResult } from '@apollo/client';
import { mapPaginatedObjectsToObjects } from '@/object-record/utils/mapPaginatedObjectsToObjects';
import { EntitiesForMultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { QueryMode, SortOrder } from '~/generated/graphql';
type SelectStringKeys<T> = NonNullable<
{
[K in keyof T]: K extends '__typename'
? never
: T[K] extends string | undefined | null
? K
: never;
}[keyof T]
>;
type ExtractEntityTypeFromQueryResponse<T> = T extends {
searchResults: Array<infer U>;
}
? U
: never;
type SearchFilter = { fieldNames: string[]; filter: string | number };
const DEFAULT_SEARCH_REQUEST_LIMIT = 10;
// TODO: use this for all search queries, because we need selectedEntities and entitiesToSelect each time we want to search
// Filtered entities to select are
export const useFilteredSearchEntityQueryV2 = ({
queryHook,
orderByField,
filters,
sortOrder = SortOrder.Asc,
selectedIds,
mappingFunction,
limit,
excludeEntityIds = [],
objectNamePlural,
}: {
queryHook: (
queryOptions?: QueryHookOptions<any, any>,
) => QueryResult<any, any>;
orderByField: string;
filters: SearchFilter[];
sortOrder?: SortOrder;
selectedIds: string[];
mappingFunction: (entity: any) => EntityForSelect;
limit?: number;
excludeEntityIds?: string[];
objectNamePlural: string;
}): EntitiesForMultipleEntitySelect<EntityForSelect> => {
const { loading: selectedEntitiesLoading, data: selectedEntitiesData } =
queryHook({
variables: {
where: {
id: {
in: selectedIds,
},
},
orderBy: {
[orderByField]: sortOrder,
},
} as any,
});
const searchFilter = filters.map(({ fieldNames, filter }) => {
return {
OR: fieldNames.map((fieldName) => ({
[fieldName]: {
startsWith: `%${filter}%`,
mode: QueryMode.Insensitive,
},
})),
};
});
const {
loading: filteredSelectedEntitiesLoading,
data: filteredSelectedEntitiesData,
} = queryHook({
variables: {
where: {
AND: [
{
AND: searchFilter,
},
{
id: {
in: selectedIds,
},
},
],
},
orderBy: {
[orderByField]: sortOrder,
},
} as any,
});
const { loading: entitiesToSelectLoading, data: entitiesToSelectData } =
queryHook({
variables: {
where: {
AND: [
{
AND: searchFilter,
},
{
id: {
notIn: [...selectedIds, ...excludeEntityIds],
},
},
],
},
limit: limit ?? DEFAULT_SEARCH_REQUEST_LIMIT,
orderBy: {
[orderByField]: sortOrder,
},
} as any,
});
console.log({
selectedEntitiesData,
test: mapPaginatedObjectsToObjects({
objectNamePlural: objectNamePlural,
pagedObjects: selectedEntitiesData,
}),
});
return {
selectedEntities: mapPaginatedObjectsToObjects({
objectNamePlural: objectNamePlural,
pagedObjects: selectedEntitiesData,
}).map(mappingFunction),
filteredSelectedEntities: mapPaginatedObjectsToObjects({
objectNamePlural: objectNamePlural,
pagedObjects: filteredSelectedEntitiesData,
}).map(mappingFunction),
entitiesToSelect: mapPaginatedObjectsToObjects({
objectNamePlural: objectNamePlural,
pagedObjects: entitiesToSelectData,
}).map(mappingFunction),
loading:
entitiesToSelectLoading ||
filteredSelectedEntitiesLoading ||
selectedEntitiesLoading,
};
};

View File

@ -5,6 +5,7 @@ import debounce from 'lodash.debounce';
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { TextInput } from '@/ui/input/components/TextInput';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { useUpdateUserMutation } from '~/generated/graphql';
@ -30,9 +31,14 @@ export const NameFields = ({
onLastNameUpdate,
}: NameFieldsProps) => {
const currentUser = useRecoilValue(currentUserState);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const [firstName, setFirstName] = useState(currentUser?.firstName ?? '');
const [lastName, setLastName] = useState(currentUser?.lastName ?? '');
const [firstName, setFirstName] = useState(
currentWorkspaceMember?.firstName ?? '',
);
const [lastName, setLastName] = useState(
currentWorkspaceMember?.lastName ?? '',
);
const [updateUser] = useUpdateUserMutation();
@ -69,13 +75,13 @@ export const NameFields = ({
}, 500);
useEffect(() => {
if (!currentUser) {
if (!currentWorkspaceMember) {
return;
}
if (
currentUser.firstName !== firstName ||
currentUser.lastName !== lastName
currentWorkspaceMember.firstName !== firstName ||
currentWorkspaceMember.lastName !== lastName
) {
debouncedUpdate();
}
@ -83,7 +89,14 @@ export const NameFields = ({
return () => {
debouncedUpdate.cancel();
};
}, [firstName, lastName, currentUser, debouncedUpdate, autoSave]);
}, [
firstName,
lastName,
currentUser,
debouncedUpdate,
autoSave,
currentWorkspaceMember,
]);
return (
<StyledComboInputContainer>

View File

@ -1,8 +1,9 @@
import { useState } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import { useRecoilState } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { ImageInput } from '@/ui/input/components/ImageInput';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { getImageAbsoluteURIOrBase64 } from '@/users/utils/getProfilePictureAbsoluteURI';
@ -16,6 +17,8 @@ export const ProfilePictureUploader = () => {
useUploadProfilePictureMutation();
const [removePicture] = useRemoveProfilePictureMutation();
const [currentUser] = useRecoilState(currentUserState);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const [uploadController, setUploadController] =
useState<AbortController | null>(null);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
@ -69,7 +72,7 @@ export const ProfilePictureUploader = () => {
return (
<ImageInput
picture={getImageAbsoluteURIOrBase64(currentUser?.avatarUrl)}
picture={getImageAbsoluteURIOrBase64(currentWorkspaceMember?.avatarUrl)}
onUpload={handleUpload}
onRemove={handleRemove}
onAbort={handleAbort}

View File

@ -1,6 +1,6 @@
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar';
import { Toggle } from '@/ui/input/components/Toggle';
import { useUpdateAllowImpersonationMutation } from '~/generated/graphql';
@ -8,7 +8,7 @@ import { useUpdateAllowImpersonationMutation } from '~/generated/graphql';
export const ToggleField = () => {
const { enqueueSnackBar } = useSnackBar();
const currentUser = useRecoilValue(currentUserState);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const [updateAllowImpersonation] = useUpdateAllowImpersonationMutation();
@ -32,7 +32,7 @@ export const ToggleField = () => {
return (
<Toggle
value={currentUser?.workspaceMember?.allowImpersonation}
value={currentWorkspaceMember?.allowImpersonation}
onChange={handleChange}
/>
);

View File

@ -2,9 +2,9 @@ import { useCallback, useEffect, useState } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import styled from '@emotion/styled';
import debounce from 'lodash.debounce';
import { useRecoilState } from 'recoil';
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { TextInput } from '@/ui/input/components/TextInput';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { useUpdateWorkspaceMutation } from '~/generated/graphql';
@ -27,10 +27,11 @@ export const NameField = ({
autoSave = true,
onNameUpdate,
}: NameFieldProps) => {
const [currentUser] = useRecoilState(currentUserState);
const workspace = currentUser?.workspaceMember?.workspace;
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const [displayName, setDisplayName] = useState(workspace?.displayName ?? '');
const [displayName, setDisplayName] = useState(
currentWorkspace?.displayName ?? '',
);
const [updateWorkspace] = useUpdateWorkspaceMutation();

View File

@ -1,7 +1,7 @@
import { getOperationName } from '@apollo/client/utilities';
import { useRecoilState } from 'recoil';
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { ImageInput } from '@/ui/input/components/ImageInput';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { getImageAbsoluteURIOrBase64 } from '@/users/utils/getProfilePictureAbsoluteURI';
@ -13,7 +13,8 @@ import {
export const WorkspaceLogoUploader = () => {
const [uploadLogo] = useUploadWorkspaceLogoMutation();
const [removeLogo] = useRemoveWorkspaceLogoMutation();
const [currentUser] = useRecoilState(currentUserState);
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const onUpload = async (file: File) => {
if (!file) {
return;
@ -34,9 +35,7 @@ export const WorkspaceLogoUploader = () => {
return (
<ImageInput
picture={getImageAbsoluteURIOrBase64(
currentUser?.workspaceMember?.workspace.logo,
)}
picture={getImageAbsoluteURIOrBase64(currentWorkspace?.logo)}
onUpload={onUpload}
onRemove={onRemove}
/>

View File

@ -8,7 +8,7 @@ import { Avatar, AvatarType } from '@/users/components/Avatar';
import { Chip, ChipVariant } from './Chip';
type EntityChipProps = {
export type EntityChipProps = {
linkToEntity?: string;
entityId: string;
name: string;

View File

@ -62,9 +62,13 @@ export const SingleEntitySelectBase = <
const entitiesInDropdown = [selectedEntity, ...entitiesToSelect].filter(
(entity): entity is CustomEntityForSelect =>
assertNotNull(entity) && isNonEmptyString(entity.name.trim()),
assertNotNull(entity) && isNonEmptyString(entity.name),
);
console.log({
entitiesInDropdown,
});
const { preselectedOptionId, resetScroll } = useEntitySelectScroll({
selectableOptionIds: [
EmptyButtonId,

View File

@ -5,6 +5,7 @@ export enum Entity {
Company = 'Company',
Person = 'Person',
User = 'User',
WorkspaceMember = 'WorkspaceMember',
}
export type EntityTypeForSelect =

View File

@ -2,6 +2,7 @@ import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { getImageAbsoluteURIOrBase64 } from '@/users/utils/getProfilePictureAbsoluteURI';
import NavCollapseButton from './NavCollapseButton';
@ -53,8 +54,8 @@ const NavWorkspaceButton = ({
showCollapseButton,
}: NavWorkspaceButtonProps) => {
const currentUser = useRecoilValue(currentUserState);
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const currentWorkspace = currentUser?.workspaceMember?.workspace;
const DEFAULT_LOGO =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACb0lEQVR4nO2VO4taQRTHr3AblbjxEVlwCwVhg7BoqqCIjy/gAyyFWNlYBOxsfH0KuxgQGwXRUkGuL2S7i1barGAgiwbdW93SnGOc4BonPiKahf3DwXFmuP/fPM4ZlvmlTxAhCBdzHnEQWYiv7Mr4C3NeuVYhQYDPzOUUQgDLBQGcLHNhvQK8DACPx8PTxiqVyvISG43GbyaT6Qfpn06n0m63e/tPAPF4vJ1MJu8kEsnWTCkWi1yr1RKGw+GDRqPBOTfr44vFQvD7/Q/lcpmaaVQAr9fLp1IpO22c47hGOBz+MB6PH+Vy+VYDAL8qlUoGtVotzOfzq4MAgsHgE/6KojiQyWR/bKVSqbSszHFM8Pl8z1YK48JsNltCOBwOnrYLO+8AAIjb+nHbycoTiUQfDJ7tFq4YAHiVSmXBxcD41u8flQU8z7fhzO0r83atVns3Go3u9Xr9x0O/RQXo9/tsIBBg6vX606a52Wz+bZ7P5/WwG29gxSJzhKgA6XTaDoFNF+krFAocmC//4yWEcSf2wTm7mCO19xFgSsKOLI16vV7b7XY7mRNoLwA0JymJ5uQIzgIAuX5PzDElT2m+E8BqtQ4ymcx7Yq7T6a6ZE4sKgOadTucaCwkxp1UzlEKh0GDxIXOwDWHAdi6Xe3swQDQa/Q7mywoolUpvsaptymazDWKxmBHTlWXZm405BFZoNpuGgwEmk4mE2SGtVivii4f1AO7J3ZopkQCQj7Ar1FeRChCJRJzVapX6DKNIfSc1Ax+wtQWQ55h6bH8FWDfYV4fO3wlwDr0C/BcADYiTPCxHqIEA2QsCZAkAKnRGkMbKN/sTX5YHPQ1e7SkAAAAASUVORK5CYII=';

View File

@ -3,6 +3,10 @@ import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import {
CurrentWorkspaceMember,
currentWorkspaceMemberState,
} from '@/auth/states/currentWorkspaceMemberState';
import { supportChatState } from '@/client-config/states/supportChatState';
import { IconHelpCircle } from '@/ui/display/icon';
import { Button } from '@/ui/input/button/components/Button';
@ -30,13 +34,18 @@ const insertScript = ({
const SupportChat = () => {
const currentUser = useRecoilValue(currentUserState);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const supportChat = useRecoilValue(supportChatState);
const [isFrontChatLoaded, setIsFrontChatLoaded] = useState(false);
const configureFront = useCallback(
(
chatId: string,
currentUser: Pick<User, 'email' | 'displayName' | 'supportUserHash'>,
currentUser: Pick<User, 'email' | 'supportUserHash'>,
currentWorkspaceMember: Pick<
CurrentWorkspaceMember,
'firstName' | 'lastName'
>,
) => {
const url = 'https://chat-assets.frontapp.com/v1/chat.bundle.js';
const script = document.querySelector(`script[src="${url}"]`);
@ -49,7 +58,10 @@ const SupportChat = () => {
chatId,
useDefaultLauncher: false,
email: currentUser.email,
name: currentUser.displayName,
name:
currentWorkspaceMember.firstName +
' ' +
currentWorkspaceMember.lastName,
userHash: currentUser?.supportUserHash,
});
setIsFrontChatLoaded(true);
@ -65,9 +77,14 @@ const SupportChat = () => {
supportChat?.supportDriver === 'front' &&
supportChat.supportFrontChatId &&
currentUser?.email &&
currentWorkspaceMember &&
!isFrontChatLoaded
) {
configureFront(supportChat.supportFrontChatId, currentUser);
configureFront(
supportChat.supportFrontChatId,
currentUser,
currentWorkspaceMember,
);
}
}, [
configureFront,
@ -75,6 +92,7 @@ const SupportChat = () => {
isFrontChatLoaded,
supportChat?.supportDriver,
supportChat.supportFrontChatId,
currentWorkspaceMember,
]);
return isFrontChatLoaded ? (

View File

@ -105,13 +105,16 @@ export const usePersistField = () => {
valueToPersist,
);
console.log({
fieldName,
valueToPersist,
});
updateEntity?.({
variables: {
where: { id: entityId },
data: {
[fieldName]: valueToPersist
? { connect: { id: valueToPersist.id } }
: { disconnect: true },
[fieldName]: valueToPersist?.id,
},
},
});

View File

@ -1,24 +1,27 @@
import { EntityChip } from '@/ui/display/chip/components/EntityChip';
import { getEntityChipFromFieldMetadata } from '@/ui/object/field/meta-types/display/utils/getEntityChipFromFieldMetadata';
import { useRelationField } from '../../hooks/useRelationField';
export const RelationFieldDisplay = () => {
const { fieldValue, fieldDefinition } = useRelationField();
const { entityChipDisplayMapper } = fieldDefinition;
if (!entityChipDisplayMapper) {
throw new Error(
"Missing entityChipDisplayMapper in FieldContext. Please provide it in the FieldContextProvider's value prop.",
);
}
const { name, pictureUrl, avatarType } =
entityChipDisplayMapper?.(fieldValue);
console.log({
fieldDefinition,
fieldValue,
});
const entityChipProps = getEntityChipFromFieldMetadata(
fieldDefinition,
fieldValue,
);
return (
<EntityChip
entityId={fieldValue?.id}
name={name}
pictureUrl={pictureUrl}
avatarType={avatarType}
entityId={entityChipProps.entityId}
name={entityChipProps.name}
pictureUrl={entityChipProps.pictureUrl}
avatarType={entityChipProps.avatarType}
/>
);
};

View File

@ -0,0 +1,32 @@
import { EntityChipProps } from '@/ui/display/chip/components/EntityChip';
import { FieldDefinition } from '@/ui/object/field/types/FieldDefinition';
import { FieldRelationMetadata } from '@/ui/object/field/types/FieldMetadata';
export const getEntityChipFromFieldMetadata = (
fieldDefinition: FieldDefinition<FieldRelationMetadata>,
fieldValue: any,
) => {
const { fieldName } = fieldDefinition.metadata;
const chipValue: Pick<
EntityChipProps,
'name' | 'pictureUrl' | 'avatarType' | 'entityId'
> = {
name: '',
pictureUrl: '',
avatarType: 'rounded',
entityId: fieldValue?.id,
};
console.log({
fieldName,
fieldValue,
});
// TODO: use every
if (fieldName === 'accountOwner' && fieldValue) {
chipValue.name = fieldValue.firstName + ' ' + fieldValue.lastName;
}
return chipValue;
};

View File

@ -22,6 +22,11 @@ export const useRelationField = () => {
}),
);
console.log({
fieldDefinition,
fieldValue,
});
const fieldInitialValue = useFieldInitialValue();
const initialSearchValue = fieldInitialValue?.isEmpty

View File

@ -40,7 +40,7 @@ export const RelationFieldInput = ({
return (
<StyledRelationPickerContainer>
{fieldDefinition.metadata.relationType === Entity.Person ? (
{fieldDefinition.metadata.fieldName === 'person' ? (
<PeoplePicker
personId={initialValue?.id ?? ''}
companyId={initialValue?.companyId ?? ''}
@ -48,7 +48,7 @@ export const RelationFieldInput = ({
onCancel={onCancel}
initialSearchFilter={initialSearchValue}
/>
) : fieldDefinition.metadata.relationType === Entity.User ? (
) : fieldDefinition.metadata.fieldName === 'accountOwner' ? (
<UserPicker
userId={initialValue?.id ?? ''}
onSubmit={handleSubmit}

View File

@ -82,6 +82,8 @@ type RecordTableProps = {
export const RecordTable = ({ updateEntityMutation }: RecordTableProps) => {
const tableBodyRef = useRef<HTMLDivElement>(null);
console.log('record table');
const {
leaveTableFocus,
setRowSelectedState,

View File

@ -1,105 +1,32 @@
import { useCallback } from 'react';
import { useRecoilState } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import {
ColorScheme,
useUpdateOneWorkspaceMemberMutation,
useUpdateUserMutation,
} from '~/generated/graphql';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord';
import { ColorScheme } from '~/generated/graphql';
export const useColorScheme = () => {
const [currentUser, setCurrentUser] = useRecoilState(currentUserState);
const [currentWorkspaceMember] = useRecoilState(currentWorkspaceMemberState);
const [updateUser] = useUpdateUserMutation();
const [updateWorkspaceMember] = useUpdateOneWorkspaceMemberMutation();
const colorScheme =
!currentUser?.workspaceMember.settings?.colorScheme &&
!currentUser?.settings?.colorScheme
? ColorScheme.System
: currentUser.workspaceMember.settings?.colorScheme ??
currentUser.settings.colorScheme;
const { updateOneObject: updateOneWorkspaceMember } =
useUpdateOneObjectRecord({
objectNamePlural: 'workspaceMembersV2',
});
const colorScheme = currentWorkspaceMember?.colorScheme ?? ColorScheme.System;
const setColorScheme = useCallback(
async (value: ColorScheme) => {
try {
// connect settings to workspace member if not already connected
await updateWorkspaceMember({
variables: {
where: { id: currentUser?.workspaceMember.id },
data: { settings: { connect: { id: currentUser?.settings.id } } },
},
});
const result = await updateUser({
variables: {
where: {
id: currentUser?.id,
},
data: {
settings: {
update: {
colorScheme: value,
},
},
},
},
optimisticResponse: currentUser
? {
__typename: 'Mutation',
updateUser: {
__typename: 'User',
...currentUser,
workspaceMember: {
...currentUser.workspaceMember,
settings: {
__typename: 'UserSettings',
id: currentUser.settings.id,
colorScheme: value,
locale: currentUser.settings.locale,
},
},
settings: {
__typename: 'UserSettings',
id: currentUser.settings.id,
colorScheme: value,
locale: currentUser.settings.locale,
},
},
}
: undefined,
update: (_cache, { data }) => {
if (
data?.updateUser.workspaceMember?.settings?.colorScheme &&
currentUser
) {
setCurrentUser({
...currentUser,
workspaceMember: {
...currentUser.workspaceMember,
settings: {
...currentUser.workspaceMember.settings,
colorScheme:
data.updateUser.workspaceMember.settings.colorScheme,
},
},
settings: {
...currentUser.settings,
colorScheme:
data.updateUser.workspaceMember.settings.colorScheme,
},
});
}
},
});
if (!result.data || result.errors) {
throw result.errors;
}
} catch (err) {}
if (!currentWorkspaceMember) {
return;
}
await updateOneWorkspaceMember?.({
idToUpdate: currentWorkspaceMember?.id,
input: {
colorScheme: value,
},
});
},
[updateWorkspaceMember, currentUser, updateUser, setCurrentUser],
[currentWorkspaceMember, updateOneWorkspaceMember],
);
return {

View File

@ -1,13 +1,14 @@
import { useEffect } from 'react';
import { useQuery } from '@apollo/client';
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
import { useFilteredSearchEntityQueryV2 } from '@/search/hooks/useFilteredSearchEntityQueryV2';
import { IconUserCircle } from '@/ui/display/icon';
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useSearchUserQuery } from '~/generated/graphql';
export type UserPickerProps = {
userId: string;
@ -18,7 +19,7 @@ export type UserPickerProps = {
};
type UserForSelect = EntityForSelect & {
entityType: Entity.User;
entityType: Entity.WorkspaceMember;
};
export const UserPicker = ({
@ -35,29 +36,39 @@ export const UserPicker = ({
setRelationPickerSearchFilter(initialSearchFilter ?? '');
}, [initialSearchFilter, setRelationPickerSearchFilter]);
const users = useFilteredSearchEntityQuery({
queryHook: useSearchUserQuery,
const { findManyQuery } = useFindOneObjectMetadataItem({
objectNamePlural: 'workspaceMembersV2',
});
const useFindManyWorkspaceMembers = () => useQuery(findManyQuery, {});
// TODO: put workspace member
const users = useFilteredSearchEntityQueryV2({
queryHook: useFindManyWorkspaceMembers,
filters: [
{
fieldNames: ['firstName', 'lastName'],
filter: relationPickerSearchFilter,
},
],
orderByField: 'firstName',
mappingFunction: (user) => ({
entityType: Entity.User,
id: user.id,
name: user.displayName,
orderByField: '',
mappingFunction: (workspaceMember) => ({
entityType: Entity.WorkspaceMember,
id: workspaceMember.id,
name: workspaceMember.firstName,
avatarType: 'rounded',
avatarUrl: user.avatarUrl ?? '',
originalEntity: user,
avatarUrl: '',
originalEntity: workspaceMember,
}),
selectedIds: userId ? [userId] : [],
objectNamePlural: 'workspaceMembersV2',
});
const handleEntitySelected = async (
selectedUser: UserForSelect | null | undefined,
) => {
console.log({
users,
});
const handleEntitySelected = async (selectedUser: any | null | undefined) => {
onSubmit(selectedUser ?? null);
};

View File

@ -1,29 +1,71 @@
import { useEffect, useState } from 'react';
import { useRecoilState } from 'recoil';
import { useApolloClient } from '@apollo/client';
import { useSetRecoilState } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { useGetCurrentUserQuery } from '~/generated/graphql';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { FIND_ONE_WORKSPACE_MEMBER_V2 } from '@/object-record/graphql/queries/findOneWorkspaceMember';
import {
useGetCurrentUserQuery,
useGetCurrentWorkspaceQuery,
} from '~/generated/graphql';
export const UserProvider = ({ children }: React.PropsWithChildren) => {
const [, setCurrentUser] = useRecoilState(currentUserState);
const [isLoading, setIsLoading] = useState(true);
const [isWorkspaceMemberLoading, setIsWorkspaceMemberLoading] =
useState(true);
const apolloClient = useApolloClient();
const { data, loading } = useGetCurrentUserQuery();
const setCurrentUser = useSetRecoilState(currentUserState);
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
useEffect(() => {
if (!loading) {
setIsLoading(false);
}
if (data?.currentUser?.workspaceMember?.settings) {
setCurrentUser({
...data.currentUser,
workspaceMember: {
...data.currentUser.workspaceMember,
settings: data.currentUser.workspaceMember.settings,
const { data: userData, loading: userLoading } = useGetCurrentUserQuery({
onCompleted: async (data) => {
const workspaceMember = await apolloClient.query({
query: FIND_ONE_WORKSPACE_MEMBER_V2,
variables: {
filter: {
userId: { eq: data.currentUser.id },
},
},
});
setCurrentWorkspaceMember(
workspaceMember.data.workspaceMembersV2.edges[0].node,
);
setIsWorkspaceMemberLoading(false);
},
onError: () => {
setIsWorkspaceMemberLoading(false);
},
});
const { data: workspaceData, loading: workspaceLoading } =
useGetCurrentWorkspaceQuery();
useEffect(() => {
if (!userLoading && !workspaceLoading && !isWorkspaceMemberLoading) {
setIsLoading(false);
}
}, [setCurrentUser, data, isLoading, loading]);
if (userData?.currentUser) {
setCurrentUser(userData.currentUser);
}
if (workspaceData?.currentWorkspace) {
setCurrentWorkspace(workspaceData.currentWorkspace);
}
}, [
setCurrentUser,
isLoading,
userLoading,
workspaceLoading,
userData?.currentUser,
workspaceData?.currentWorkspace,
setCurrentWorkspace,
isWorkspaceMemberLoading,
]);
return isLoading ? <></> : <>{children}</>;
};

View File

@ -5,52 +5,6 @@ export const UPDATE_USER = gql`
updateUser(data: $data, where: $where) {
id
email
displayName
firstName
lastName
avatarUrl
workspaceMember {
id
workspace {
id
domainName
displayName
logo
inviteHash
}
assignedActivities {
id
title
}
authoredActivities {
id
title
}
authoredAttachments {
id
name
type
}
settings {
id
colorScheme
locale
}
companies {
id
name
domainName
}
comments {
id
body
}
}
settings {
id
locale
colorScheme
}
}
}
`;

View File

@ -4,16 +4,7 @@ export const GET_CURRENT_USER = gql`
query GetCurrentUser {
currentUser {
...userFieldsFragment
avatarUrl
canImpersonate
workspaceMember {
...workspaceMemberFieldsFragment
}
settings {
id
locale
colorScheme
}
supportUserHash
}
}

View File

@ -0,0 +1,6 @@
export type WorkspaceMember = {
id: string;
firstName: string;
lastName: string;
avatarUrl: string;
};

View File

@ -2,7 +2,7 @@ import styled from '@emotion/styled';
import { OverflowingTextWithTooltip } from '@/ui/display/tooltip/OverflowingTextWithTooltip';
import { Avatar } from '@/users/components/Avatar';
import { User } from '~/generated/graphql';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
const StyledContainer = styled.div`
background: ${({ theme }) => theme.background.secondary};
@ -29,12 +29,7 @@ const StyledEmailText = styled.span`
`;
type WorkspaceMemberCardProps = {
workspaceMember: {
user: Pick<
User,
'id' | 'firstName' | 'lastName' | 'displayName' | 'avatarUrl' | 'email'
>;
};
workspaceMember: WorkspaceMember;
accessory?: React.ReactNode;
};
@ -44,15 +39,19 @@ export const WorkspaceMemberCard = ({
}: WorkspaceMemberCardProps) => (
<StyledContainer>
<Avatar
avatarUrl={workspaceMember.user.avatarUrl}
colorId={workspaceMember.user.id}
placeholder={workspaceMember.user.firstName || ''}
avatarUrl={workspaceMember.avatarUrl}
colorId={workspaceMember.id}
placeholder={workspaceMember.firstName || ''}
type="squared"
size="xl"
/>
<StyledContent>
<OverflowingTextWithTooltip text={workspaceMember.user.displayName} />
<StyledEmailText>{workspaceMember.user.email}</StyledEmailText>
<OverflowingTextWithTooltip
text={workspaceMember.firstName + ' ' + workspaceMember.lastName}
/>
<StyledEmailText>
{workspaceMember.firstName + ' ' + workspaceMember.lastName}
</StyledEmailText>
</StyledContent>
{accessory}

View File

@ -1,42 +0,0 @@
import { gql } from '@apollo/client';
export const WORKSPACE_MEMBER_FIELDS_FRAGMENT = gql`
fragment workspaceMemberFieldsFragment on WorkspaceMember {
id
allowImpersonation
workspace {
id
domainName
displayName
logo
inviteHash
}
assignedActivities {
id
title
}
authoredActivities {
id
title
}
authoredAttachments {
id
name
type
}
settings {
id
colorScheme
locale
}
companies {
id
name
domainName
}
comments {
id
body
}
}
`;

View File

@ -1,9 +0,0 @@
import { gql } from '@apollo/client';
export const REMOVE_WORKSPACE_MEMBER = gql`
mutation RemoveWorkspaceMember($where: WorkspaceMemberWhereUniqueInput!) {
deleteWorkspaceMember(where: $where) {
id
}
}
`;

View File

@ -1,12 +0,0 @@
import { gql } from '@apollo/client';
export const UPDATE_WORKSPACE_MEMBER = gql`
mutation UpdateOneWorkspaceMember(
$data: WorkspaceMemberUpdateInput!
$where: WorkspaceMemberWhereUniqueInput!
) {
UpdateOneWorkspaceMember(data: $data, where: $where) {
...workspaceMemberFieldsFragment
}
}
`;

View File

@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
export const GET_CURRENT_WORKSPACE = gql`
query getCurrentWorkspace {
currentWorkspace {
id
displayName
logo
}
}
`;

View File

@ -4,10 +4,6 @@ export const GET_WORKSPACE_MEMBERS = gql`
query GetWorkspaceMembers($where: WorkspaceMemberWhereInput) {
workspaceMembers: findManyWorkspaceMember(where: $where) {
id
user {
...userFieldsFragment
avatarUrl
}
}
}
`;

View File

@ -11,6 +11,7 @@ import { z } from 'zod';
import { SubTitle } from '@/auth/components/SubTitle';
import { Title } from '@/auth/components/Title';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { H2Title } from '@/ui/display/typography/components/H2Title';
@ -57,6 +58,7 @@ export const CreateProfile = () => {
const { enqueueSnackBar } = useSnackBar();
const [currentUser] = useRecoilState(currentUserState);
const [currentWorkspaceMember] = useRecoilState(currentWorkspaceMemberState);
const [updateUser] = useUpdateUserMutation();
@ -69,8 +71,8 @@ export const CreateProfile = () => {
} = useForm<Form>({
mode: 'onChange',
defaultValues: {
firstName: currentUser?.firstName ?? '',
lastName: currentUser?.lastName ?? '',
firstName: currentWorkspaceMember?.firstName ?? '',
lastName: currentWorkspaceMember?.lastName ?? '',
},
resolver: zodResolver(validationSchema),
});

View File

@ -1,15 +1,18 @@
import { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilValue } from 'recoil';
import { useAuth } from '@/auth/hooks/useAuth';
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { AppPath } from '../../modules/types/AppPath';
export const VerifyEffect = () => {
const [searchParams] = useSearchParams();
const loginToken = searchParams.get('loginToken');
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const isLogged = useIsLogged();
const navigate = useNavigate();
@ -21,13 +24,9 @@ export const VerifyEffect = () => {
if (!loginToken) {
navigate(AppPath.SignIn);
} else {
const verifyResponse = await verify(loginToken);
await verify(loginToken);
if (
isNonEmptyString(
verifyResponse.user.workspaceMember?.workspace.displayName,
)
) {
if (isNonEmptyString(currentWorkspace?.displayName)) {
navigate(AppPath.Index);
} else {
navigate(AppPath.CreateWorkspace);

View File

@ -38,21 +38,9 @@ export const ImpersonateEffect = () => {
throw new Error('No impersonate result');
}
if (!impersonateResult.data?.impersonate.user.workspaceMember) {
throw new Error('No workspace member');
}
if (!impersonateResult.data?.impersonate.user.workspaceMember.settings) {
throw new Error('No workspace member settings');
}
setCurrentUser({
...impersonateResult.data.impersonate.user,
workspaceMember: {
...impersonateResult.data.impersonate.user.workspaceMember,
settings:
impersonateResult.data.impersonate.user.workspaceMember.settings,
},
// Todo also set WorkspaceMember
});
setTokenPair(impersonateResult.data?.impersonate.tokens);

View File

@ -1,8 +1,10 @@
import { useState } from 'react';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useDeleteOneObjectRecord } from '@/object-record/hooks/useDeleteOneObjectRecord';
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { IconSettings, IconTrash } from '@/ui/display/icon';
import { H1Title } from '@/ui/display/typography/components/H1Title';
@ -13,10 +15,7 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'
import { Section } from '@/ui/layout/section/components/Section';
import { WorkspaceInviteLink } from '@/workspace/components/WorkspaceInviteLink';
import { WorkspaceMemberCard } from '@/workspace/components/WorkspaceMemberCard';
import {
useGetWorkspaceMembersQuery,
useRemoveWorkspaceMemberMutation,
} from '~/generated/graphql';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
const StyledH1Title = styled(H1Title)`
margin-bottom: 0;
@ -31,51 +30,22 @@ const StyledButtonContainer = styled.div`
export const SettingsWorkspaceMembers = () => {
const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false);
const [userToDelete, setUserToDelete] = useState<string | undefined>();
const [workspaceMemberToDelete, setWorkspaceMemberToDelete] = useState<
string | undefined
>();
const [currentUser] = useRecoilState(currentUserState);
const workspace = currentUser?.workspaceMember?.workspace;
const { data } = useGetWorkspaceMembersQuery();
const [removeWorkspaceMember] = useRemoveWorkspaceMemberMutation();
const handleRemoveWorkspaceMember = async (userId: string) => {
await removeWorkspaceMember({
variables: {
where: {
userId,
},
},
optimisticResponse: {
__typename: 'Mutation',
deleteWorkspaceMember: {
__typename: 'WorkspaceMember',
id: userId,
},
},
update: (cache, { data: responseData }) => {
if (!responseData) {
return;
}
cache.evict({
id: cache.identify({
id: responseData.deleteWorkspaceMember.id,
__typename: 'WorkspaceMember',
}),
});
cache.evict({
id: cache.identify({
id: userId,
__typename: 'User',
}),
});
cache.gc();
},
const { objects: workspaceMembers } =
useFindManyObjectRecords<WorkspaceMember>({
objectNamePlural: 'workspaceMembersV2',
});
const { deleteOneObject: deleteOneWorkspaceMember } =
useDeleteOneObjectRecord({
objectNamePlural: 'workspaceMembersV2',
});
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const handleRemoveWorkspaceMember = async (workspaceMemberId: string) => {
await deleteOneWorkspaceMember?.(workspaceMemberId);
setIsConfirmationModalOpen(false);
};
@ -83,14 +53,14 @@ export const SettingsWorkspaceMembers = () => {
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer width={350}>
<StyledH1Title title="Members" />
{workspace?.inviteHash && (
{currentWorkspace?.inviteHash && (
<Section>
<H2Title
title="Invite"
description="Send an invitation to use Twenty"
/>
<WorkspaceInviteLink
inviteLink={`${window.location.origin}/invite/${workspace?.inviteHash}`}
inviteLink={`${window.location.origin}/invite/${currentWorkspace?.inviteHash}`}
/>
</Section>
)}
@ -99,17 +69,17 @@ export const SettingsWorkspaceMembers = () => {
title="Members"
description="Manage the members of your space here"
/>
{data?.workspaceMembers?.map((member) => (
{workspaceMembers?.map((member) => (
<WorkspaceMemberCard
key={member.user.id}
workspaceMember={{ user: member.user }}
key={member.id}
workspaceMember={member as WorkspaceMember}
accessory={
currentUser?.id !== member.user.id && (
currentWorkspace?.id !== member.id && (
<StyledButtonContainer>
<IconButton
onClick={() => {
setIsConfirmationModalOpen(true);
setUserToDelete(member.user.id);
setWorkspaceMemberToDelete(member.id);
}}
variant="tertiary"
size="medium"
@ -133,7 +103,8 @@ export const SettingsWorkspaceMembers = () => {
</>
}
onConfirmClick={() =>
userToDelete && handleRemoveWorkspaceMember(userToDelete)
workspaceMemberToDelete &&
handleRemoveWorkspaceMember(workspaceMemberToDelete)
}
deleteButtonText="Delete account"
/>

View File

@ -1,14 +1,14 @@
import { useEffect } from 'react';
import { useRecoilState } from 'recoil';
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useFilter } from '@/ui/object/object-filter-dropdown/hooks/useFilter';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { tasksFilterDefinitions } from './tasks-filter-definitions';
export const TasksEffect = () => {
const [currentUser] = useRecoilState(currentUserState);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { setSelectedFilter, setAvailableFilterDefinitions } = useFilter();
useEffect(() => {
@ -16,16 +16,19 @@ export const TasksEffect = () => {
}, [setAvailableFilterDefinitions]);
useEffect(() => {
if (currentUser) {
if (currentWorkspaceMember) {
setSelectedFilter({
fieldMetadataId: 'assigneeId',
value: currentUser.id,
value: currentWorkspaceMember.id,
operand: ViewFilterOperand.Is,
displayValue: currentUser.displayName,
displayAvatarUrl: currentUser.avatarUrl ?? undefined,
displayValue:
currentWorkspaceMember.firstName +
' ' +
currentWorkspaceMember.lastName,
displayAvatarUrl: currentWorkspaceMember.avatarUrl ?? undefined,
definition: tasksFilterDefinitions[0],
});
}
}, [currentUser, setSelectedFilter]);
}, [currentWorkspaceMember, setSelectedFilter]);
return <></>;
};

View File

@ -58,12 +58,9 @@ export class AbilityFactory {
);
// User
can(AbilityAction.Read, 'User', {
workspaceMember: {
workspaceId: workspace.id,
},
});
if (user) {
can(AbilityAction.Read, 'User', { id: user.id });
can(AbilityAction.Update, 'User', { id: user.id });
can(AbilityAction.Delete, 'User', { id: user.id });
} else {

View File

@ -12,7 +12,6 @@ import { TokenExpiredError, JsonWebTokenError, verify } from 'jsonwebtoken';
import { AppService } from './app.service';
import { CoreModule } from './core/core.module';
import { CoreV2Module } from './coreV2/core.module';
import { IntegrationsModule } from './integrations/integrations.module';
import { PrismaModule } from './database/prisma.module';
import { HealthModule } from './health/health.module';
@ -103,7 +102,6 @@ import { ExceptionFilter } from './filters/exception.filter';
AbilityModule,
IntegrationsModule,
CoreModule,
CoreV2Module,
TenantModule,
],
providers: [

View File

@ -130,7 +130,6 @@ export class AuthResolver {
defaultFields: {
User: {
id: true,
workspaceMember: { select: { allowImpersonation: true } },
},
},
})
@ -140,7 +139,6 @@ export class AuthResolver {
assert(user.canImpersonate, 'User cannot impersonate', ForbiddenException);
const select = prismaSelect.valueOf('user') as Prisma.UserSelect & {
id: true;
workspaceMember: { select: { allowImpersonation: true } };
};
return this.authService.impersonate(impersonateInput.userId, select);

View File

@ -63,11 +63,6 @@ export class GoogleAuthController {
firstName: firstName ?? '',
lastName: lastName ?? '',
locale: 'en',
settings: {
create: {
locale: 'en',
},
},
},
},
workspaceId,

View File

@ -88,7 +88,6 @@ export class AuthService {
data: {
email: signUpInput.email,
passwordHash,
locale: 'en',
},
} as Prisma.UserCreateArgs,
workspace.id,
@ -160,11 +159,6 @@ export class AuthService {
userId: string,
select: Prisma.UserSelect & {
id: true;
workspaceMember: {
select: {
allowImpersonation: true;
};
};
},
) {
const user = await this.userService.findUnique({
@ -175,11 +169,8 @@ export class AuthService {
});
assert(user, "This user doesn't exist", NotFoundException);
assert(
user.workspaceMember?.allowImpersonation,
'Impersonation not allowed',
ForbiddenException,
);
// Todo: check if workspace member can be impersonated
const accessToken = await this.tokenService.generateAccessToken(user.id);
const refreshToken = await this.tokenService.generateRefreshToken(user.id);

View File

@ -33,22 +33,19 @@ export class TokenService {
const user = await this.prismaService.client.user.findUnique({
where: { id: userId },
include: {
workspaceMember: true,
},
});
if (!user) {
throw new NotFoundException('User is not found');
}
if (!user.workspaceMember) {
throw new ForbiddenException('User is not associated to a workspace');
if (!user.defaultWorkspaceId) {
throw new NotFoundException('User does not have a default workspace');
}
const jwtPayload: JwtPayload = {
sub: user.id,
workspaceId: user.workspaceMember.workspaceId,
workspaceId: user.defaultWorkspaceId,
};
return {

View File

@ -1,6 +1,8 @@
import { Module } from '@nestjs/common';
import { WebHookModule } from 'src/core/web-hook/web-hook.module';
import { UserModule as UserV2Module } from 'src/coreV2/user/user.module';
import { RefreshTokenModule as RefreshTokenV2Module } from 'src/coreV2/refresh-token/refresh-token.module';
import { UserModule } from './user/user.module';
import { CommentModule } from './comment/comment.module';
@ -34,6 +36,8 @@ import { ApiKeyModule } from './api-key/api-key.module';
FavoriteModule,
ApiKeyModule,
WebHookModule,
UserV2Module,
RefreshTokenV2Module,
],
exports: [
AuthModule,
@ -48,6 +52,8 @@ import { ApiKeyModule } from './api-key/api-key.module';
FavoriteModule,
ApiKeyModule,
WebHookModule,
UserV2Module,
RefreshTokenV2Module,
],
})
export class CoreModule {}

View File

@ -66,13 +66,6 @@ export class UserService {
: await this.workspaceService.createDefaultWorkspace();
assert(workspace, 'workspace is missing', BadRequestException);
const userSettings = await this.prismaService.client.userSettings.create({
data: { locale: 'en' },
});
const settings = { connect: { id: userSettings.id } };
// Create user
const user = await this.prismaService.client.user.upsert({
where: {
@ -80,17 +73,7 @@ export class UserService {
},
create: {
...(args.data as Prisma.UserCreateInput),
settings,
workspaceMember: {
create: {
workspace: {
connect: { id: workspace.id },
},
settings,
},
},
locale: 'en',
defaultWorkspaceId: workspace.id,
},
update: {},
...(args.select ? { select: args.select } : {}),

View File

@ -1,31 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { GraphQLModule } from '@nestjs/graphql';
import { YogaDriverConfig, YogaDriver } from '@graphql-yoga/nestjs';
import GraphQLJSON from 'graphql-type-json';
// eslint-disable-next-line no-restricted-imports
import config from '../../ormconfig';
import { UserModule } from './user/user.module';
import { RefreshTokenModule } from './refresh-token/refresh-token.module';
@Module({
imports: [
TypeOrmModule.forRoot(config),
GraphQLModule.forRoot<YogaDriverConfig>({
context: ({ req }) => ({ req }),
driver: YogaDriver,
autoSchemaFile: true,
include: [CoreV2Module],
resolvers: { JSON: GraphQLJSON },
plugins: [],
path: '/graphqlv2',
}),
UserModule,
RefreshTokenModule,
],
exports: [UserModule],
})
export class CoreV2Module {}

View File

@ -3,7 +3,6 @@ import {
PagingStrategies,
ReadResolverOpts,
} from '@ptc-org/nestjs-query-graphql';
import { SortDirection } from '@ptc-org/nestjs-query-core';
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
@ -26,7 +25,8 @@ export const refreshTokenAutoResolverOpts: AutoResolverOpts<
enableTotalCount: true,
pagingStrategy: PagingStrategies.CURSOR,
read: {
defaultSort: [{ field: 'id', direction: SortDirection.DESC }],
many: { disabled: true },
one: { disabled: true },
},
create: {
many: { disabled: true },

View File

@ -15,12 +15,12 @@ import {
IDField,
} from '@ptc-org/nestjs-query-graphql';
import { User } from 'src/coreV2/user/user.entity';
import { UserV2 } from 'src/coreV2/user/user.entity';
import { BeforeCreateOneRefreshToken } from './hooks/before-create-one-refresh-token.hook';
@Entity('refresh_tokens')
@ObjectType('RefreshToken')
@ObjectType('refreshTokenV2')
@BeforeCreateOne(BeforeCreateOneRefreshToken)
@Authorize({
authorize: (context: any) => ({
@ -32,9 +32,9 @@ export class RefreshToken {
@PrimaryGeneratedColumn('uuid')
id: string;
@ManyToOne(() => User, (user) => user.refreshTokens)
@ManyToOne(() => UserV2, (user) => user.refreshTokens)
@JoinColumn({ name: 'userId' })
user: User;
user: UserV2;
@Column()
userId: string;

View File

@ -1,8 +1,12 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
// eslint-disable-next-line no-restricted-imports
import config from '../../../ormconfig';
import { RefreshToken } from './refresh-token.entity';
import { refreshTokenAutoResolverOpts } from './refresh-token.auto-resolver-opts';
@ -10,6 +14,7 @@ import { RefreshTokenService } from './services/refresh-token.service';
@Module({
imports: [
TypeOrmModule.forRoot(config),
NestjsQueryGraphQLModule.forFeature({
imports: [NestjsQueryTypeOrmModule.forFeature([RefreshToken])],
services: [RefreshTokenService],

View File

@ -4,12 +4,12 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { Repository } from 'typeorm';
import { assert } from 'src/utils/assert';
import { User } from 'src/coreV2/user/user.entity';
import { UserV2 } from 'src/coreV2/user/user.entity';
export class UserService extends TypeOrmQueryService<User> {
export class UserService extends TypeOrmQueryService<UserV2> {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
@InjectRepository(UserV2)
private readonly userRepository: Repository<UserV2>,
) {
super(userRepository);
}

View File

@ -1,14 +1,12 @@
import { SortDirection } from '@ptc-org/nestjs-query-core';
import {
AutoResolverOpts,
ReadResolverOpts,
PagingStrategies,
} from '@ptc-org/nestjs-query-graphql';
import { UserV2 } from 'src/coreV2/user/user.entity';
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
import { User } from './user.entity';
export const userAutoResolverOpts: AutoResolverOpts<
any,
any,
@ -18,12 +16,13 @@ export const userAutoResolverOpts: AutoResolverOpts<
PagingStrategies
>[] = [
{
EntityClass: User,
DTOClass: User,
EntityClass: UserV2,
DTOClass: UserV2,
enableTotalCount: true,
pagingStrategy: PagingStrategies.CURSOR,
read: {
defaultSort: [{ field: 'id', direction: SortDirection.DESC }],
many: { disabled: true },
one: { disabled: true },
},
create: {
many: { disabled: true },

View File

@ -13,8 +13,8 @@ import { GraphQLJSONObject } from 'graphql-type-json';
import { RefreshToken } from 'src/coreV2/refresh-token/refresh-token.entity';
@Entity('users')
@ObjectType('user')
@Entity('userV2')
@ObjectType('userV2')
// @Authorize({
// authorize: (context: any) => ({
// // FIXME: We do not have this relation in the database
@ -23,7 +23,7 @@ import { RefreshToken } from 'src/coreV2/refresh-token/refresh-token.entity';
// },
// }),
// })
export class User {
export class UserV2 {
@IDField(() => ID)
@PrimaryGeneratedColumn('uuid')
id: string;

View File

@ -5,8 +5,8 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { AbilityModule } from 'src/ability/ability.module';
import { FileModule } from 'src/core/file/file.module';
import { UserV2 } from 'src/coreV2/user/user.entity';
import { User } from './user.entity';
import { UserResolver } from './user.resolver';
import { userAutoResolverOpts } from './user.auto-resolver-opts';
@ -15,7 +15,7 @@ import { UserService } from './services/user.service';
@Module({
imports: [
NestjsQueryGraphQLModule.forFeature({
imports: [NestjsQueryTypeOrmModule.forFeature([User])],
imports: [NestjsQueryTypeOrmModule.forFeature([UserV2])],
services: [UserService],
resolvers: userAutoResolverOpts,
}),

View File

@ -26,8 +26,7 @@ import { FileUploadService } from 'src/core/file/services/file-upload.service';
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
import { assert } from 'src/utils/assert';
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
import { User } from './user.entity';
import { UserV2 } from 'src/coreV2/user/user.entity';
import { UserService } from './services/user.service';
@ -39,7 +38,7 @@ const getHMACKey = (email?: string, key?: string | null) => {
};
@UseGuards(JwtAuthGuard)
@Resolver(() => User)
@Resolver(() => UserV2)
export class UserResolver {
constructor(
private readonly userService: UserService,
@ -47,8 +46,8 @@ export class UserResolver {
private readonly fileUploadService: FileUploadService,
) {}
@Query(() => User)
async currentUser(@AuthUser() { id }: User) {
@Query(() => UserV2)
async currentUserV2(@AuthUser() { id }: UserV2) {
const user = await this.userService.findById(id);
assert(user, 'User not found');
return user;
@ -57,14 +56,14 @@ export class UserResolver {
@ResolveField(() => String, {
nullable: false,
})
displayName(@Parent() parent: User): string {
displayName(@Parent() parent: UserV2): string {
return `${parent.firstName ?? ''} ${parent.lastName ?? ''}`;
}
@ResolveField(() => String, {
nullable: true,
})
supportUserHash(@Parent() parent: User): string | null {
supportUserHash(@Parent() parent: UserV2): string | null {
if (this.environmentService.getSupportDriver() !== SupportDriver.Front) {
return null;
}
@ -73,8 +72,8 @@ export class UserResolver {
}
@Mutation(() => String)
async uploadProfilePicture(
@AuthUser() { id }: User,
async uploadProfilePictureV2(
@AuthUser() { id }: UserV2,
@Args({ name: 'file', type: () => GraphQLUpload })
{ createReadStream, filename, mimetype }: FileUpload,
): Promise<string> {
@ -96,11 +95,11 @@ export class UserResolver {
return paths[0];
}
@Mutation(() => User)
@Mutation(() => UserV2)
@UseGuards(AbilityGuard)
@CheckAbilities(DeleteUserAbilityHandler)
async deleteUserAccount(
@AuthUser() { id: userId }: User,
async deleteUserV2(
@AuthUser() { id: userId }: UserV2,
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
return this.userService.deleteUser({ userId, workspaceId });

View File

@ -0,0 +1,5 @@
-- DropForeignKey
ALTER TABLE "workspace_members" DROP CONSTRAINT "workspace_members_userId_fkey";
-- AlterTable
ALTER TABLE "users" ADD COLUMN "defaultWorkspaceId" TEXT;

View File

@ -0,0 +1,20 @@
/*
Warnings:
- You are about to drop the column `settingsId` on the `users` table. All the data in the column will be lost.
*/
-- DropForeignKey
ALTER TABLE "users" DROP CONSTRAINT "users_settingsId_fkey";
-- DropIndex
DROP INDEX "users_settingsId_key";
-- AlterTable
ALTER TABLE "user_settings" ADD COLUMN "userId" TEXT;
-- AlterTable
ALTER TABLE "users" DROP COLUMN "settingsId";
-- AddForeignKey
ALTER TABLE "user_settings" ADD CONSTRAINT "user_settings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@ -0,0 +1,11 @@
/*
Warnings:
- You are about to drop the column `userId` on the `user_settings` table. All the data in the column will be lost.
*/
-- DropForeignKey
ALTER TABLE "user_settings" DROP CONSTRAINT "user_settings_userId_fkey";
-- AlterTable
ALTER TABLE "user_settings" DROP COLUMN "userId";

View File

@ -102,18 +102,15 @@ model User {
/// @Validator.IsOptional()
canImpersonate Boolean @default(false)
/// @TypeGraphQL.omit(input: true)
workspaceMember WorkspaceMember?
companies Company[]
companies Company[]
/// @TypeGraphQL.omit(input: true, output: true)
refreshTokens RefreshToken[]
comments Comment[]
refreshTokens RefreshToken[]
comments Comment[]
defaultWorkspaceId String?
authoredActivities Activity[] @relation(name: "authoredActivities")
assignedActivities Activity[] @relation(name: "assignedActivities")
authoredAttachments Attachment[] @relation(name: "authoredAttachments")
settings UserSettings @relation(fields: [settingsId], references: [id])
settingsId String @unique
/// @TypeGraphQL.omit(input: true, output: true)
deletedAt DateTime?
@ -138,7 +135,6 @@ model UserSettings {
/// @Validator.IsString()
locale String
user User?
WorkspaceMember WorkspaceMember[]
createdAt DateTime @default(now())
@ -195,7 +191,6 @@ model WorkspaceMember {
/// @Validator.IsOptional()
allowImpersonation Boolean @default(true)
user User @relation(fields: [userId], references: [id])
userId String @unique
/// @TypeGraphQL.omit(input: true, output: false)
workspace Workspace @relation(fields: [workspaceId], references: [id])

View File

@ -19,19 +19,8 @@ export const seedUsers = async (prisma: PrismaClient) => {
locale: 'en',
passwordHash:
'$2b$10$66d.6DuQExxnrfI9rMqOg.U1XIYpagr6Lv05uoWLYbYmtK0HDIvS6', // Applecar2025
settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-9dcb1084c109',
avatarUrl: null,
workspaceMember: {
connectOrCreate: {
where: {
id: '20202020-0687-4c41-b707-ed1bfca972a7',
},
create: {
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-9dcb1084c109',
},
},
},
defaultWorkspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
},
});
@ -52,15 +41,8 @@ export const seedUsers = async (prisma: PrismaClient) => {
lastName: 'Ive',
email: 'jony.ive@apple.dev',
locale: 'en',
settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-2c4a2035a215',
avatarUrl: null,
workspaceMember: {
create: {
id: '20202020-77d5-4cb6-b60a-f4a835a85d61',
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-2c4a2035a215',
},
},
defaultWorkspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
},
});
@ -81,15 +63,8 @@ export const seedUsers = async (prisma: PrismaClient) => {
lastName: 'Schiler',
email: 'phil.schiler@apple.dev',
locale: 'en',
settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-8e1f2097b328',
avatarUrl: null,
workspaceMember: {
create: {
id: '20202020-1553-45c6-a028-5a9064cce07f',
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-8e1f2097b328',
},
},
defaultWorkspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
},
});
@ -110,14 +85,6 @@ export const seedUsers = async (prisma: PrismaClient) => {
lastName: 'Bochet',
email: 'charles@twenty.dev',
locale: 'en',
settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-5e2d1049c430',
workspaceMember: {
create: {
id: 'twenty-dev-7ed9d213-1c25-4d02-bf35-6aeccf7oa419',
workspaceId: 'twenty-dev-7ed9d212-1c25-4d02-bf25-6aeccf7ea420',
settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-5e2d1049c430',
},
},
},
});
};

View File

@ -8,6 +8,7 @@ const fieldMetadataTableName = 'fieldMetadata';
export enum SeedWorkspaceMemberFieldMetadataIds {
FirstName = '20202020-1fa8-4d38-9fa4-0cf696909298',
LastName = '20202020-8c37-4163-ba06-1dada334ce3e',
AvatarUrl = '20202020-7ba6-40d5-934b-17146183a212',
Locale = '20202020-10f6-4df9-8d6f-a760b65bd800',
ColorScheme = '20202020-83f2-4c5f-96b0-0c51ecc160e3',
AllowImpersonation = '20202020-bb19-44a1-8156-8866f87a5f42',
@ -77,6 +78,22 @@ export const seedWorkspaceMemberFieldMetadata = async (
icon: 'IconCircleUser',
isNullable: false,
},
{
id: SeedWorkspaceMemberFieldMetadataIds.AvatarUrl,
objectMetadataId: SeedObjectMetadataIds.WorkspaceMember,
isCustom: false,
workspaceId: SeedWorkspaceId,
isActive: true,
type: 'TEXT',
name: 'avatarUrl',
label: 'Avatar Url',
targetColumnMap: {
value: 'avatarUrl',
},
description: 'Workspace member avatar',
icon: 'IconFileUpload',
isNullable: true,
},
{
id: SeedWorkspaceMemberFieldMetadataIds.UserId,
objectMetadataId: SeedObjectMetadataIds.WorkspaceMember,
@ -123,7 +140,7 @@ export const seedWorkspaceMemberFieldMetadata = async (
},
description: 'Preferred color scheme',
icon: 'IconColorSwatch',
isNullable: false,
isNullable: true,
},
{
id: SeedWorkspaceMemberFieldMetadataIds.Locale,

View File

@ -37,7 +37,7 @@ export const seedWorkspaceMember = async (
firstName: 'Tim',
lastName: 'Apple',
locale: 'en',
colorScheme: 'light',
colorScheme: 'Light',
allowImpersonation: true,
userId: WorkspaceMemberUserIds.Tim,
},
@ -46,7 +46,7 @@ export const seedWorkspaceMember = async (
firstName: 'Jony',
lastName: 'Ive',
locale: 'en',
colorScheme: 'light',
colorScheme: 'Light',
allowImpersonation: true,
userId: WorkspaceMemberUserIds.Jony,
},
@ -55,9 +55,9 @@ export const seedWorkspaceMember = async (
firstName: 'Phil',
lastName: 'Shiler',
locale: 'en',
colorScheme: 'light',
colorScheme: 'Light',
allowImpersonation: true,
userId: WorkspaceMemberUserIds.Phil,
userId: WorkspaceMemberUserIds.Tim,
},
])
.execute();

View File

@ -1,4 +1,10 @@
import { ObjectType, ID, Field, HideField } from '@nestjs/graphql';
import {
ObjectType,
ID,
Field,
HideField,
registerEnumType,
} from '@nestjs/graphql';
import { CreateDateColumn, UpdateDateColumn } from 'typeorm';
import {
@ -11,6 +17,11 @@ import {
import { ObjectMetadataDTO } from 'src/metadata/object-metadata/dtos/object-metadata.dto';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
registerEnumType(RelationMetadataType, {
name: 'RelationMetadataType',
description: 'Type of the relation',
});
@ObjectType('relation')
@Authorize({
authorize: (context: any) => ({

View File

@ -1,5 +1,3 @@
import { registerEnumType } from '@nestjs/graphql';
import {
Column,
CreateDateColumn,
@ -22,11 +20,6 @@ export enum RelationMetadataType {
MANY_TO_MANY = 'MANY_TO_MANY',
}
registerEnumType(RelationMetadataType, {
name: 'RelationMetadataType',
description: 'Type of the relation',
});
@Entity('relationMetadata')
export class RelationMetadataEntity implements RelationMetadataInterface {
@PrimaryGeneratedColumn('uuid')

View File

@ -22,6 +22,11 @@ export const addWorkspaceMemberTable: TenantMigrationTableAction[] = [
columnType: 'varchar',
action: TenantMigrationColumnActionType.CREATE,
},
{
columnName: 'avatarUrl',
columnType: 'varchar',
action: TenantMigrationColumnActionType.CREATE,
},
{
columnName: 'colorScheme',
columnType: 'varchar',

View File

@ -0,0 +1,51 @@
import { EntityManager } from 'typeorm';
export const companyPrefillData = async (
entityManager: EntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.insert()
.into(`${schemaName}.company`, [
'name',
'domainName',
'address',
'employees',
])
.orIgnore()
.values([
{
name: 'Airbnb',
domainName: 'airbnb.com',
address: 'San Francisco',
employees: 5000,
},
{
name: 'Qonto',
domainName: 'qonto.com',
address: 'San Francisco',
employees: 800,
},
{
name: 'Stripe',
domainName: 'stripe.com',
address: 'San Francisco',
employees: 8000,
},
{
name: 'Figma',
domainName: 'figma.com',
address: 'San Francisco',
employees: 800,
},
{
name: 'Notion',
domainName: 'notion.com',
address: 'San Francisco',
employees: 400,
},
])
.returning('*')
.execute();
};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,41 @@
import { EntityManager } from 'typeorm';
export const pipelineStepPrefillData = async (
entityManager: EntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.insert()
.into(`${schemaName}.pipelineStep`, ['name', 'color', 'position'])
.orIgnore()
.values([
{
name: 'New',
color: 'red',
position: 0,
},
{
name: 'Screening',
color: 'purple',
position: 1,
},
{
name: 'Meeting',
color: 'sky',
position: 2,
},
{
name: 'Proposal',
color: 'turquoise',
position: 3,
},
{
name: 'Customer',
color: 'yellow',
position: 4,
},
])
.returning('*')
.execute();
};

View File

@ -1,269 +1,31 @@
import { DataSource, EntityManager } from 'typeorm';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { viewPrefillData } from 'src/tenant-manager/standard-objects-prefill-data/view';
import { companyPrefillData } from 'src/tenant-manager/standard-objects-prefill-data/company';
import { personPrefillData } from 'src/tenant-manager/standard-objects-prefill-data/person';
import { pipelineStepPrefillData } from 'src/tenant-manager/standard-objects-prefill-data/pipeline-step';
export const standardObjectsPrefillData = async (
workspaceDataSource: DataSource,
schemaName: string,
objectMetadata: ObjectMetadataEntity[],
) => {
const objectMetadataMap = objectMetadata.reduce((acc, object) => {
acc[object.nameSingular] = {
id: object.id,
fields: object.fields.reduce((acc, field) => {
acc[field.name] = field.id;
return acc;
}, {}),
};
return acc;
}, {});
workspaceDataSource.transaction(async (entityManager: EntityManager) => {
const createdCompanies = await entityManager
.createQueryBuilder()
.insert()
.into(`${schemaName}.company`, [
'name',
'domainName',
'address',
'employees',
])
.orIgnore()
.values([
{
name: 'Airbnb',
domainName: 'airbnb.com',
address: 'San Francisco',
employees: 5000,
},
{
name: 'Qonto',
domainName: 'qonto.com',
address: 'San Francisco',
employees: 800,
},
{
name: 'Stripe',
domainName: 'stripe.com',
address: 'San Francisco',
employees: 8000,
},
{
name: 'Figma',
domainName: 'figma.com',
address: 'San Francisco',
employees: 800,
},
{
name: 'Notion',
domainName: 'notion.com',
address: 'San Francisco',
employees: 400,
},
])
.returning('*')
.execute();
const companyIdMap = createdCompanies.raw.reduce((acc, view) => {
acc[view.name] = view.id;
return acc;
}, {});
const createdViews = await entityManager
.createQueryBuilder()
.insert()
.into(`${schemaName}.view`, ['name', 'objectMetadataId', 'type'])
.orIgnore()
.values([
{
name: 'All companies',
objectMetadataId: 'company',
type: 'table',
},
{
name: 'All people',
objectMetadataId: 'person',
type: 'table',
},
{
name: 'All opportunities',
objectMetadataId: 'company',
type: 'kanban',
},
{
name: 'All Companies (V2)',
objectMetadataId: companyIdMap['Airbnb'],
type: 'table',
},
])
.returning('*')
.execute();
const viewIdMap = createdViews.raw.reduce((acc, view) => {
acc[view.name] = view.id;
return acc;
}, {});
await entityManager
.createQueryBuilder()
.insert()
.into(`${schemaName}.viewField`, [
'fieldMetadataId',
'viewId',
'position',
'isVisible',
'size',
])
.orIgnore()
.values([
{
fieldMetadataId: 'name',
viewId: viewIdMap['All Companies (V2)'],
position: 0,
isVisible: true,
size: 180,
},
{
fieldMetadataId: 'name',
viewId: viewIdMap['All companies'],
position: 0,
isVisible: true,
size: 180,
},
{
fieldMetadataId: 'domainName',
viewId: viewIdMap['All companies'],
position: 1,
isVisible: true,
size: 100,
},
{
fieldMetadataId: 'accountOwner',
viewId: viewIdMap['All companies'],
position: 2,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'createdAt',
viewId: viewIdMap['All companies'],
position: 3,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'employees',
viewId: viewIdMap['All companies'],
position: 4,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'linkedin',
viewId: viewIdMap['All companies'],
position: 5,
isVisible: true,
size: 170,
},
{
fieldMetadataId: 'address',
viewId: viewIdMap['All companies'],
position: 6,
isVisible: true,
size: 170,
},
{
fieldMetadataId: 'displayName',
viewId: viewIdMap['All people'],
position: 0,
isVisible: true,
size: 210,
},
{
fieldMetadataId: 'email',
viewId: viewIdMap['All people'],
position: 1,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'company',
viewId: viewIdMap['All people'],
position: 2,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'phone',
viewId: viewIdMap['All people'],
position: 3,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'createdAt',
viewId: viewIdMap['All people'],
position: 4,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'city',
viewId: viewIdMap['All people'],
position: 5,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'jobTitle',
viewId: viewIdMap['All people'],
position: 6,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'linkedin',
viewId: viewIdMap['All people'],
position: 7,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'x',
viewId: viewIdMap['All people'],
position: 8,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'amount',
viewId: viewIdMap['All opportunities'],
position: 0,
isVisible: true,
size: 180,
},
{
fieldMetadataId: 'probability',
viewId: viewIdMap['All opportunities'],
position: 1,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'closeDate',
viewId: viewIdMap['All opportunities'],
position: 2,
isVisible: true,
size: 100,
},
{
fieldMetadataId: 'company',
viewId: viewIdMap['All opportunities'],
position: 3,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'createdAt',
viewId: viewIdMap['All opportunities'],
position: 4,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'pointOfContact',
viewId: viewIdMap['All opportunities'],
position: 5,
isVisible: true,
size: 150,
},
])
.execute();
await companyPrefillData(entityManager, schemaName);
await personPrefillData(entityManager, schemaName);
await viewPrefillData(entityManager, schemaName, objectMetadataMap);
await pipelineStepPrefillData(entityManager, schemaName);
});
};

View File

@ -0,0 +1,278 @@
import { EntityManager } from 'typeorm';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
export const viewPrefillData = async (
entityManager: EntityManager,
schemaName: string,
objectMetadataMap: Record<string, ObjectMetadataEntity>,
) => {
// Creating views
const createdViews = await entityManager
.createQueryBuilder()
.insert()
.into(`${schemaName}.view`, ['name', 'objectMetadataId', 'type'])
.orIgnore()
.values([
{
name: 'All companies',
objectMetadataId: 'company',
type: 'table',
},
{
name: 'All people',
objectMetadataId: 'person',
type: 'table',
},
{
name: 'All opportunities',
objectMetadataId: 'company',
type: 'kanban',
},
{
name: 'All Companies (V2)',
objectMetadataId: objectMetadataMap['companyV2'].id,
type: 'table',
},
{
name: 'All People (V2)',
objectMetadataId: objectMetadataMap['personV2'].id,
type: 'table',
},
{
name: 'All Opportunities (V2)',
objectMetadataId: objectMetadataMap['companyV2'].id,
type: 'kanban',
},
])
.returning('*')
.execute();
const viewIdMap = createdViews.raw.reduce((acc, view) => {
acc[view.name] = view.id;
return acc;
}, {});
// Creating viewFields
await entityManager
.createQueryBuilder()
.insert()
.into(`${schemaName}.viewField`, [
'fieldMetadataId',
'viewId',
'position',
'isVisible',
'size',
])
.orIgnore()
.values([
// CompanyV2
{
fieldMetadataId: objectMetadataMap['companyV2'].fields['name'],
viewId: viewIdMap['All Companies (V2)'],
position: 0,
isVisible: true,
size: 180,
},
{
fieldMetadataId: objectMetadataMap['companyV2'].fields['domainName'],
viewId: viewIdMap['All Companies (V2)'],
position: 1,
isVisible: true,
size: 100,
},
// {
// fieldMetadataId: objectMetadataMap['companyV2'].fields['accountOwner'],
// viewId: viewIdMap['All Companies (V2)'],
// position: 2,
// isVisible: true,
// size: 150,
// },
// {
// fieldMetadataId: 'createdAt',
// viewId: viewIdMap['All Companies (V2)'],
// position: 3,
// isVisible: true,
// size: 150,
// },
{
fieldMetadataId: objectMetadataMap['companyV2'].fields['employees'],
viewId: viewIdMap['All Companies (V2)'],
position: 4,
isVisible: true,
size: 150,
},
{
fieldMetadataId: objectMetadataMap['companyV2'].fields['linkedinUrl'],
viewId: viewIdMap['All Companies (V2)'],
position: 5,
isVisible: true,
size: 170,
},
{
fieldMetadataId: objectMetadataMap['companyV2'].fields['address'],
viewId: viewIdMap['All Companies (V2)'],
position: 6,
isVisible: true,
size: 170,
},
// PeopleV2
{
fieldMetadataId: objectMetadataMap['personV2'].fields['firstName'], // TODO: change to displayName once we have name field type
viewId: viewIdMap['All People (V2)'],
position: 0,
isVisible: true,
size: 210,
},
{
fieldMetadataId: objectMetadataMap['personV2'].fields['email'],
viewId: viewIdMap['All People (V2)'],
position: 1,
isVisible: true,
size: 150,
},
// {
// fieldMetadataId: objectMetadataMap['personV2'].fields['company'],
// viewId: viewIdMap['All People (V2)'],
// position: 2,
// isVisible: true,
// size: 150,
// },
{
fieldMetadataId: objectMetadataMap['personV2'].fields['phone'],
viewId: viewIdMap['All People (V2)'],
position: 3,
isVisible: true,
size: 150,
},
// {
// fieldMetadataId: 'createdAt',
// viewId: viewIdMap['All People (V2)'],
// position: 4,
// isVisible: true,
// size: 150,
// },
{
fieldMetadataId: objectMetadataMap['personV2'].fields['city'],
viewId: viewIdMap['All People (V2)'],
position: 5,
isVisible: true,
size: 150,
},
{
fieldMetadataId: objectMetadataMap['personV2'].fields['jobTitle'],
viewId: viewIdMap['All People (V2)'],
position: 6,
isVisible: true,
size: 150,
},
{
fieldMetadataId: objectMetadataMap['personV2'].fields['linkedinUrl'],
viewId: viewIdMap['All People (V2)'],
position: 7,
isVisible: true,
size: 150,
},
{
fieldMetadataId: objectMetadataMap['personV2'].fields['xUrl'],
viewId: viewIdMap['All People (V2)'],
position: 8,
isVisible: true,
size: 150,
},
// Companies
{
fieldMetadataId: 'name',
viewId: viewIdMap['All companies'],
position: 0,
isVisible: true,
size: 180,
},
{
fieldMetadataId: 'domainName',
viewId: viewIdMap['All companies'],
position: 1,
isVisible: true,
size: 100,
},
{
fieldMetadataId: 'accountOwner',
viewId: viewIdMap['All companies'],
position: 2,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'createdAt',
viewId: viewIdMap['All companies'],
position: 3,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'employees',
viewId: viewIdMap['All companies'],
position: 4,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'linkedin',
viewId: viewIdMap['All companies'],
position: 5,
isVisible: true,
size: 170,
},
{
fieldMetadataId: 'address',
viewId: viewIdMap['All companies'],
position: 6,
isVisible: true,
size: 170,
},
// Opportunities
{
fieldMetadataId: 'amount',
viewId: viewIdMap['All opportunities'],
position: 0,
isVisible: true,
size: 180,
},
{
fieldMetadataId: 'probability',
viewId: viewIdMap['All opportunities'],
position: 1,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'closeDate',
viewId: viewIdMap['All opportunities'],
position: 2,
isVisible: true,
size: 100,
},
{
fieldMetadataId: 'company',
viewId: viewIdMap['All opportunities'],
position: 3,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'createdAt',
viewId: viewIdMap['All opportunities'],
position: 4,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'pointOfContact',
viewId: viewIdMap['All opportunities'],
position: 5,
isVisible: true,
size: 150,
},
])
.execute();
};

Some files were not shown because too many files have changed in this diff Show More