Skip to content

Commit 4fa7a3c

Browse files
FelixMalfaitAdityaPimpalkar
authored andcommitted
Add profile pictures to people and fix account/workspace deletion (twentyhq#984)
* Fix LinkedIn URL not redirecting to the right url * add avatars for people and seeds * Fix delete account/workspace * Add people picture on other pages * Change style of delete button * Revert modal to previous size * Fix tests
1 parent 2e3bd08 commit 4fa7a3c

File tree

24 files changed

+181
-90
lines changed

24 files changed

+181
-90
lines changed

front/src/generated/graphql.tsx

+14-4
Original file line numberDiff line numberDiff line change
@@ -1276,6 +1276,7 @@ export type Person = {
12761276
ActivityTarget?: Maybe<Array<ActivityTarget>>;
12771277
_activityCount: Scalars['Int'];
12781278
activities: Array<Activity>;
1279+
avatarUrl?: Maybe<Scalars['String']>;
12791280
city?: Maybe<Scalars['String']>;
12801281
comments: Array<Comment>;
12811282
company?: Maybe<Company>;
@@ -1296,6 +1297,7 @@ export type Person = {
12961297
export type PersonCreateInput = {
12971298
Favorite?: InputMaybe<FavoriteCreateNestedManyWithoutPersonInput>;
12981299
ActivityTarget?: InputMaybe<ActivityTargetCreateNestedManyWithoutPersonInput>;
1300+
avatarUrl?: InputMaybe<Scalars['String']>;
12991301
city?: InputMaybe<Scalars['String']>;
13001302
company?: InputMaybe<CompanyCreateNestedOneWithoutPeopleInput>;
13011303
createdAt?: InputMaybe<Scalars['DateTime']>;
@@ -1335,6 +1337,7 @@ export type PersonOrderByRelationAggregateInput = {
13351337
export type PersonOrderByWithRelationInput = {
13361338
Favorite?: InputMaybe<FavoriteOrderByRelationAggregateInput>;
13371339
ActivityTarget?: InputMaybe<ActivityTargetOrderByRelationAggregateInput>;
1340+
avatarUrl?: InputMaybe<SortOrder>;
13381341
city?: InputMaybe<SortOrder>;
13391342
company?: InputMaybe<CompanyOrderByWithRelationInput>;
13401343
companyId?: InputMaybe<SortOrder>;
@@ -1356,6 +1359,7 @@ export type PersonRelationFilter = {
13561359
};
13571360

13581361
export enum PersonScalarFieldEnum {
1362+
AvatarUrl = 'avatarUrl',
13591363
City = 'city',
13601364
CompanyId = 'companyId',
13611365
CreatedAt = 'createdAt',
@@ -1374,6 +1378,7 @@ export enum PersonScalarFieldEnum {
13741378
export type PersonUpdateInput = {
13751379
Favorite?: InputMaybe<FavoriteUpdateManyWithoutPersonNestedInput>;
13761380
ActivityTarget?: InputMaybe<ActivityTargetUpdateManyWithoutPersonNestedInput>;
1381+
avatarUrl?: InputMaybe<Scalars['String']>;
13771382
city?: InputMaybe<Scalars['String']>;
13781383
company?: InputMaybe<CompanyUpdateOneWithoutPeopleNestedInput>;
13791384
createdAt?: InputMaybe<Scalars['DateTime']>;
@@ -1411,6 +1416,7 @@ export type PersonWhereInput = {
14111416
ActivityTarget?: InputMaybe<ActivityTargetListRelationFilter>;
14121417
NOT?: InputMaybe<Array<PersonWhereInput>>;
14131418
OR?: InputMaybe<Array<PersonWhereInput>>;
1419+
avatarUrl?: InputMaybe<StringNullableFilter>;
14141420
city?: InputMaybe<StringNullableFilter>;
14151421
company?: InputMaybe<CompanyRelationFilter>;
14161422
companyId?: InputMaybe<StringNullableFilter>;
@@ -2492,7 +2498,7 @@ export type GetPeopleQueryVariables = Exact<{
24922498
}>;
24932499

24942500

2495-
export type GetPeopleQuery = { __typename?: 'Query', people: Array<{ __typename?: 'Person', id: string, phone?: string | null, email?: string | null, city?: string | null, firstName?: string | null, lastName?: string | null, displayName: string, jobTitle?: string | null, linkedinUrl?: string | null, createdAt: string, _activityCount: number, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null }> };
2501+
export type GetPeopleQuery = { __typename?: 'Query', people: Array<{ __typename?: 'Person', id: string, phone?: string | null, email?: string | null, city?: string | null, firstName?: string | null, lastName?: string | null, displayName: string, jobTitle?: string | null, linkedinUrl?: string | null, avatarUrl?: string | null, createdAt: string, _activityCount: number, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null }> };
24962502

24972503
export type GetPersonPhoneByIdQueryVariables = Exact<{
24982504
id: Scalars['String'];
@@ -2548,7 +2554,7 @@ export type GetPersonQueryVariables = Exact<{
25482554
}>;
25492555

25502556

2551-
export type GetPersonQuery = { __typename?: 'Query', findUniquePerson: { __typename?: 'Person', id: string, firstName?: string | null, lastName?: string | null, displayName: string, email?: string | null, createdAt: string, city?: string | null, jobTitle?: string | null, linkedinUrl?: string | null, phone?: string | null, _activityCount: number, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null } };
2557+
export type GetPersonQuery = { __typename?: 'Query', findUniquePerson: { __typename?: 'Person', id: string, firstName?: string | null, lastName?: string | null, displayName: string, email?: string | null, createdAt: string, city?: string | null, jobTitle?: string | null, linkedinUrl?: string | null, avatarUrl?: string | null, phone?: string | null, _activityCount: number, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null } };
25522558

25532559
export type UpdateOnePersonMutationVariables = Exact<{
25542560
where: PersonWhereUniqueInput;
@@ -2585,7 +2591,7 @@ export type GetPipelineProgressQueryVariables = Exact<{
25852591
}>;
25862592

25872593

2588-
export type GetPipelineProgressQuery = { __typename?: 'Query', findManyPipelineProgress: Array<{ __typename?: 'PipelineProgress', id: string, pipelineStageId: string, progressableType: PipelineProgressableType, progressableId: string, amount?: number | null, closeDate?: string | null, pointOfContactId?: string | null, probability?: number | null, pointOfContact?: { __typename?: 'Person', id: string, firstName?: string | null, lastName?: string | null, displayName: string } | null }> };
2594+
export type GetPipelineProgressQuery = { __typename?: 'Query', findManyPipelineProgress: Array<{ __typename?: 'PipelineProgress', id: string, pipelineStageId: string, progressableType: PipelineProgressableType, progressableId: string, amount?: number | null, closeDate?: string | null, pointOfContactId?: string | null, probability?: number | null, pointOfContact?: { __typename?: 'Person', id: string, firstName?: string | null, lastName?: string | null, displayName: string, avatarUrl?: string | null } | null }> };
25892595

25902596
export type UpdateOnePipelineProgressMutationVariables = Exact<{
25912597
id?: InputMaybe<Scalars['String']>;
@@ -2639,7 +2645,7 @@ export type SearchPeopleQueryVariables = Exact<{
26392645
}>;
26402646

26412647

2642-
export type SearchPeopleQuery = { __typename?: 'Query', searchResults: Array<{ __typename?: 'Person', id: string, phone?: string | null, email?: string | null, city?: string | null, firstName?: string | null, lastName?: string | null, displayName: string, createdAt: string }> };
2648+
export type SearchPeopleQuery = { __typename?: 'Query', searchResults: Array<{ __typename?: 'Person', id: string, phone?: string | null, email?: string | null, city?: string | null, firstName?: string | null, lastName?: string | null, displayName: string, avatarUrl?: string | null, createdAt: string }> };
26432649

26442650
export type SearchUserQueryVariables = Exact<{
26452651
where?: InputMaybe<UserWhereInput>;
@@ -3826,6 +3832,7 @@ export const GetPeopleDocument = gql`
38263832
displayName
38273833
jobTitle
38283834
linkedinUrl
3835+
avatarUrl
38293836
createdAt
38303837
_activityCount
38313838
company {
@@ -4137,6 +4144,7 @@ export const GetPersonDocument = gql`
41374144
city
41384145
jobTitle
41394146
linkedinUrl
4147+
avatarUrl
41404148
phone
41414149
_activityCount
41424150
company {
@@ -4361,6 +4369,7 @@ export const GetPipelineProgressDocument = gql`
43614369
firstName
43624370
lastName
43634371
displayName
4372+
avatarUrl
43644373
}
43654374
probability
43664375
}
@@ -4592,6 +4601,7 @@ export const SearchPeopleDocument = gql`
45924601
firstName
45934602
lastName
45944603
displayName
4604+
avatarUrl
45954605
createdAt
45964606
}
45974607
}

front/src/modules/activities/components/ActivityRelationPicker.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,12 @@ export function ActivityRelationPicker({ activity }: OwnProps) {
216216
pictureUrl={entity.avatarUrl}
217217
/>
218218
) : (
219-
<PersonChip key={entity.id} name={entity.name} id={entity.id} />
219+
<PersonChip
220+
key={entity.id}
221+
name={entity.name}
222+
id={entity.id}
223+
pictureUrl={entity.avatarUrl ?? ''}
224+
/>
220225
),
221226
)}
222227
</StyledRelationContainer>

front/src/modules/people/components/EditablePeopleFullName.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ type OwnProps = {
1010
| Partial<
1111
Pick<
1212
Person,
13-
'id' | 'firstName' | 'lastName' | 'displayName' | '_activityCount'
13+
| 'id'
14+
| 'firstName'
15+
| 'lastName'
16+
| 'displayName'
17+
| 'avatarUrl'
18+
| '_activityCount'
1419
>
1520
>
1621
| null
@@ -44,6 +49,7 @@ export function EditablePeopleFullName({
4449
<PersonChip
4550
name={`${person?.firstName ?? ''} ${person?.lastName ?? ''}`}
4651
id={person?.id ?? ''}
52+
pictureUrl={person?.avatarUrl ?? ''}
4753
/>
4854
</NoEditModeContainer>
4955
}

front/src/modules/people/hooks/useSetPeopleEntityTable.ts

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export function useSetPeopleEntityTable() {
108108
lastName: person.lastName ?? null,
109109
commentCount: person._activityCount,
110110
displayName: person.displayName ?? null,
111+
avatarUrl: person.avatarUrl ?? null,
111112
});
112113
}
113114
}

front/src/modules/people/queries/select.ts

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const GET_PEOPLE = gql`
3030
displayName
3131
jobTitle
3232
linkedinUrl
33+
avatarUrl
3334
createdAt
3435
_activityCount
3536
company {
@@ -69,6 +70,7 @@ export function useFilteredSearchPeopleQuery({
6970
id: entity.id,
7071
entityType: CommentableType.Person,
7172
name: `${entity.firstName} ${entity.lastName}`,
73+
avatarUrl: entity.avatarUrl,
7274
avatarType: 'rounded',
7375
} as CommentableEntityForSelect),
7476
searchFilter,

front/src/modules/people/queries/show.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const GET_PERSON = gql`
1414
city
1515
jobTitle
1616
linkedinUrl
17+
avatarUrl
1718
phone
1819
_activityCount
1920
company {

front/src/modules/people/states/peopleNamesFamilyState.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const peopleNameCellFamilyState = atomFamily<
66
lastName: string | null;
77
commentCount: number | null;
88
displayName: string | null;
9+
avatarUrl: string | null;
910
},
1011
string
1112
>({
@@ -15,5 +16,6 @@ export const peopleNameCellFamilyState = atomFamily<
1516
lastName: null,
1617
commentCount: null,
1718
displayName: null,
19+
avatarUrl: null,
1820
},
1921
});

front/src/modules/people/table/components/EditablePeopleFullNameCell.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ export function EditablePeopleFullNameCell() {
1313

1414
const [updatePerson] = useUpdateOnePersonMutation();
1515

16-
const { commentCount, firstName, lastName, displayName } = useRecoilValue(
17-
peopleNameCellFamilyState(currentRowEntityId ?? ''),
18-
);
16+
const { commentCount, firstName, lastName, displayName, avatarUrl } =
17+
useRecoilValue(peopleNameCellFamilyState(currentRowEntityId ?? ''));
1918

2019
return (
2120
<EditablePeopleFullName
@@ -25,6 +24,7 @@ export function EditablePeopleFullNameCell() {
2524
firstName,
2625
lastName,
2726
displayName: displayName ?? undefined,
27+
avatarUrl: avatarUrl,
2828
}}
2929
onSubmit={(newFirstValue, newSecondValue) =>
3030
updatePerson({

front/src/modules/pipeline/editable-field/components/PipelineProgressPointOfContactEditableField.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { PipelineProgressPointOfContactPickerFieldEditMode } from './PipelinePro
1010

1111
type OwnProps = {
1212
pipelineProgress: Pick<PipelineProgress, 'id' | 'pointOfContactId'> & {
13-
pointOfContact?: Pick<Person, 'id' | 'displayName'> | null;
13+
pointOfContact?: Pick<Person, 'id' | 'displayName' | 'avatarUrl'> | null;
1414
};
1515
};
1616

@@ -36,6 +36,9 @@ export function PipelineProgressPointOfContactEditableField({
3636
<PersonChip
3737
id={pipelineProgress.pointOfContact.id}
3838
name={pipelineProgress.pointOfContact.displayName}
39+
pictureUrl={
40+
pipelineProgress.pointOfContact.avatarUrl ?? undefined
41+
}
3942
/>
4043
) : (
4144
<></>

front/src/modules/pipeline/queries/select.ts

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const GET_PIPELINE_PROGRESS = gql`
4343
firstName
4444
lastName
4545
displayName
46+
avatarUrl
4647
}
4748
probability
4849
}

front/src/modules/settings/components/SettingsNavbar.tsx

+8-8
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,23 @@ export function SettingsNavbar() {
5353
/>
5454
<NavTitle label="Workspace" />
5555
<NavItem
56-
label="Members"
57-
to="/settings/workspace-members"
58-
icon={<IconUsers size={theme.icon.size.md} />}
56+
label="General"
57+
to="/settings/workspace"
58+
icon={<IconSettings size={theme.icon.size.md} />}
5959
active={
6060
!!useMatch({
61-
path: useResolvedPath('/settings/workspace-members').pathname,
61+
path: useResolvedPath('/settings/workspace').pathname,
6262
end: true,
6363
})
6464
}
6565
/>
6666
<NavItem
67-
label="General"
68-
to="/settings/workspace"
69-
icon={<IconSettings size={theme.icon.size.md} />}
67+
label="Members"
68+
to="/settings/workspace-members"
69+
icon={<IconUsers size={theme.icon.size.md} />}
7070
active={
7171
!!useMatch({
72-
path: useResolvedPath('/settings/workspace').pathname,
72+
path: useResolvedPath('/settings/workspace-members').pathname,
7373
end: true,
7474
})
7575
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { useCallback, useState } from 'react';
2+
import { useNavigate } from 'react-router-dom';
3+
4+
import { useAuth } from '@/auth/hooks/useAuth';
5+
import { AppPath } from '@/types/AppPath';
6+
import { ButtonVariant } from '@/ui/button/components/Button';
7+
import { SubSectionTitle } from '@/ui/title/components/SubSectionTitle';
8+
import { useDeleteUserAccountMutation } from '~/generated/graphql';
9+
10+
import { DeleteModal, StyledDeleteButton } from './DeleteModal';
11+
12+
export function DeleteAccount() {
13+
const [isDeleteAccountModalOpen, setIsDeleteAccountModalOpen] =
14+
useState(false);
15+
16+
const [deleteUserAccount] = useDeleteUserAccountMutation();
17+
const { signOut } = useAuth();
18+
const navigate = useNavigate();
19+
20+
const handleLogout = useCallback(() => {
21+
signOut();
22+
navigate(AppPath.SignIn);
23+
}, [signOut, navigate]);
24+
25+
const deleteAccount = async () => {
26+
await deleteUserAccount();
27+
handleLogout();
28+
};
29+
30+
return (
31+
<>
32+
<SubSectionTitle
33+
title="Danger zone"
34+
description="Delete account and all the associated data"
35+
/>
36+
37+
<StyledDeleteButton
38+
onClick={() => setIsDeleteAccountModalOpen(true)}
39+
variant={ButtonVariant.Secondary}
40+
title="Delete account"
41+
/>
42+
43+
<DeleteModal
44+
isOpen={isDeleteAccountModalOpen}
45+
setIsOpen={setIsDeleteAccountModalOpen}
46+
title="Account Deletion"
47+
subtitle={
48+
<>
49+
This action cannot be undone. This will permanently delete your
50+
entire account. <br /> Please type in your email to confirm.
51+
</>
52+
}
53+
handleConfirmDelete={deleteAccount}
54+
deleteButtonText="Delete account"
55+
/>
56+
</>
57+
);
58+
}

front/src/modules/settings/profile/components/DeleteModal.tsx

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react';
1+
import { ReactNode, useState } from 'react';
22
import styled from '@emotion/styled';
33
import { AnimatePresence, LayoutGroup } from 'framer-motion';
44
import { useRecoilValue } from 'recoil';
@@ -12,7 +12,7 @@ import { debounce } from '~/utils/debounce';
1212
interface DeleteModalProps {
1313
isOpen: boolean;
1414
title: string;
15-
subtitle: string;
15+
subtitle: ReactNode;
1616
setIsOpen: (val: boolean) => void;
1717
handleConfirmDelete: () => void;
1818
deleteButtonText?: string;
@@ -23,6 +23,10 @@ const StyledTitle = styled.div`
2323
font-weight: ${({ theme }) => theme.font.weight.semiBold};
2424
`;
2525

26+
const StyledSubtitle = styled.div`
27+
text-align: center;
28+
`;
29+
2630
const StyledModal = styled(Modal)`
2731
color: ${({ theme }) => theme.font.color.primary};
2832
> * + * {
@@ -70,22 +74,18 @@ export function DeleteModal({
7074
250,
7175
);
7276

73-
const errorMessage =
74-
email && !isValidEmail ? 'email provided is not correct' : '';
75-
7677
return (
7778
<AnimatePresence mode="wait">
7879
<LayoutGroup>
79-
<StyledModal isOpen={isOpen}>
80+
<StyledModal isOpen={isOpen} onOutsideClick={() => setIsOpen(!isOpen)}>
8081
<StyledTitle>{title}</StyledTitle>
81-
<div>{subtitle}</div>
82+
<StyledSubtitle>{subtitle}</StyledSubtitle>
8283
<TextInput
8384
value={email}
8485
onChange={handleEmailChange}
8586
placeholder={userEmail}
8687
fullWidth
8788
key={'email-' + userEmail}
88-
error={errorMessage}
8989
/>
9090
<StyledDeleteButton
9191
onClick={handleConfirmDelete}

0 commit comments

Comments
 (0)