Skip to content

Commit 5e9ee72

Browse files
authored
Merge pull request #57 from CruGlobal/MPDX-6970
MPDX-6970: Contact Details Header
2 parents dda3236 + 7727c1d commit 5e9ee72

22 files changed

Lines changed: 727 additions & 45 deletions

src/components/Contacts/ContactDetails/ContactDetails.test.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const onClose = jest.fn();
1010

1111
describe('ContactDetails', () => {
1212
it('should show loading state', async () => {
13-
const { getByText } = render(
13+
const { queryByRole } = render(
1414
<GqlMockedProvider<GetContactDetailsHeaderQuery>
1515
mocks={{ contact: { id: contactId } }}
1616
>
@@ -22,11 +22,12 @@ describe('ContactDetails', () => {
2222
</GqlMockedProvider>,
2323
);
2424

25-
await waitFor(() => expect(getByText('loading')).toBeInTheDocument());
25+
expect(queryByRole('Skeleton')).toBeInTheDocument();
26+
expect(queryByRole('ContactName')).toBeNull();
2627
});
2728

2829
it('should render with contact details', async () => {
29-
const { findAllByRole, queryByText } = render(
30+
const { findAllByRole, queryByRole } = render(
3031
<GqlMockedProvider<GetContactDetailsHeaderQuery>
3132
mocks={{ contact: { id: contactId } }}
3233
>
@@ -38,10 +39,10 @@ describe('ContactDetails', () => {
3839
</GqlMockedProvider>,
3940
);
4041

41-
await waitFor(async () =>
42-
expect((await findAllByRole('contactName'))[0]).toBeInTheDocument(),
43-
);
42+
await waitFor(async () => {
43+
expect((await findAllByRole('ContactName'))[0]).toBeInTheDocument();
44+
});
4445

45-
expect(queryByText('loading')).toBeNull();
46+
expect(queryByRole('Skeleton')).toBeNull();
4647
});
4748
});

src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.graphql

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,14 @@
11
fragment ContactDetailsHeader on Contact {
22
id
33
avatar
4-
greeting
5-
lateAt
6-
name
7-
pledgeAmount
8-
pledgeCurrency
9-
pledgeFrequency
10-
primaryAddress {
11-
street
12-
city
13-
state
14-
postalCode
15-
country
16-
}
174
primaryPerson {
185
firstName
19-
gender
206
lastName
21-
primaryEmailAddress {
22-
email
23-
}
24-
primaryPhoneNumber {
25-
number
26-
}
27-
suffix
28-
title
297
}
30-
status
8+
...ContactHeaderAddress
9+
...ContactHeaderEmail
10+
...ContactHeaderPhone
11+
...ContactHeaderStatus
3112
}
3213

3314
query GetContactDetailsHeader($accountListId: ID!, $contactId: ID!) {

src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.test.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const contactId = 'contact-1';
99

1010
describe('ContactDetails', () => {
1111
it('should show loading state', async () => {
12-
const { getByText } = render(
12+
const { queryByRole } = render(
1313
<GqlMockedProvider<GetContactDetailsHeaderQuery>>
1414
<ContactDetailsHeader
1515
accountListId={accountListId}
@@ -18,11 +18,12 @@ describe('ContactDetails', () => {
1818
</GqlMockedProvider>,
1919
);
2020

21-
await waitFor(() => expect(getByText('loading')).toBeInTheDocument());
21+
expect(queryByRole('Skeleton')).toBeInTheDocument();
22+
expect(queryByRole('ContactName')).toBeNull();
2223
});
2324

2425
it('should render with contact details', async () => {
25-
const { findAllByRole, queryByText } = render(
26+
const { findAllByRole, queryByRole } = render(
2627
<GqlMockedProvider<GetContactDetailsHeaderQuery>>
2728
<ContactDetailsHeader
2829
accountListId={accountListId}
@@ -31,10 +32,10 @@ describe('ContactDetails', () => {
3132
</GqlMockedProvider>,
3233
);
3334

34-
await waitFor(async () =>
35-
expect((await findAllByRole('contactName'))[0]).toBeInTheDocument(),
36-
);
35+
await waitFor(async () => {
36+
expect((await findAllByRole('ContactName'))[0]).toBeInTheDocument();
37+
});
3738

38-
expect(queryByText('loading')).toBeNull();
39+
expect(queryByRole('Skeleton')).toBeNull();
3940
});
4041
});
Lines changed: 118 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,139 @@
1-
import { Box } from '@material-ui/core';
1+
import { Avatar, Box, IconButton, styled, Typography } from '@material-ui/core';
2+
import { Close, MoreVert } from '@material-ui/icons';
3+
import { Skeleton } from '@material-ui/lab';
24
import React from 'react';
5+
import { useTranslation } from 'react-i18next';
6+
import theme from '../../../../theme';
37

8+
import { StarContactIcon } from '../../StarContactIcon/StarContactIcon';
49
import { useGetContactDetailsHeaderQuery } from './ContactDetailsHeader.generated';
10+
import { ContactHeaderAddressSection } from './ContactHeaderSection/ContactHeaderAddressSection';
11+
import { ContactHeaderPhoneSection } from './ContactHeaderSection/ContactHeaderPhoneSection';
12+
import { ContactHeaderEmailSection } from './ContactHeaderSection/ContactHeaderEmailSection';
13+
import { ContactHeaderStatusSection } from './ContactHeaderSection/ContactHeaderStatusSection';
514

615
interface Props {
716
accountListId: string;
817
contactId: string;
918
}
1019

20+
const HeaderBar = styled(Box)(({}) => ({
21+
display: 'flex',
22+
paddingBottom: theme.spacing(1),
23+
}));
24+
const HeaderBarContactWrap = styled(Box)(({}) => ({
25+
flex: 1,
26+
display: 'flex',
27+
alignItems: 'center',
28+
}));
29+
const HeaderBarButtonsWrap = styled(Box)(({}) => ({
30+
display: 'flex',
31+
alignItems: 'center',
32+
}));
33+
const ContactAvatar = styled(Avatar)(({}) => ({
34+
backgroundColor: theme.palette.secondary.dark,
35+
height: 64,
36+
width: 64,
37+
borderRadius: 32,
38+
}));
39+
const PrimaryContactName = styled(Typography)(({}) => ({
40+
display: 'inline',
41+
marginLeft: 18,
42+
}));
43+
const PrimaryText = styled(Typography)(({}) => ({
44+
display: 'inline',
45+
marginRight: 8,
46+
}));
47+
const ButtonWrap = styled(IconButton)(({}) => ({
48+
margin: 4,
49+
width: 32,
50+
height: 32,
51+
}));
52+
const MoreButtonIcon = styled(MoreVert)(({}) => ({
53+
width: 16,
54+
height: 16,
55+
color: theme.palette.text.primary,
56+
}));
57+
const CloseButtonIcon = styled(Close)(({}) => ({
58+
width: 14,
59+
height: 14,
60+
color: theme.palette.text.primary,
61+
}));
62+
const HeaderSectionWrap = styled(Box)(({}) => ({
63+
display: 'flex',
64+
}));
65+
1166
export const ContactDetailsHeader: React.FC<Props> = ({
1267
accountListId,
1368
contactId,
1469
}: Props) => {
1570
const { data, loading } = useGetContactDetailsHeaderQuery({
1671
variables: { accountListId, contactId },
1772
});
73+
const { t } = useTranslation();
1874

1975
return (
20-
<Box>
21-
{/*TODO: Build Header*/}
22-
{loading ? (
23-
<p>loading</p>
24-
) : (
25-
<p role="contactName">{data?.contact.name}</p>
26-
)}
76+
<Box style={{ padding: 24 }}>
77+
<HeaderBar>
78+
<ContactAvatar src={data?.contact?.avatar || ''} />
79+
<HeaderBarContactWrap>
80+
{loading ? (
81+
<Box role="Skeleton">
82+
<Skeleton
83+
variant="text"
84+
style={{
85+
display: 'inline',
86+
marginLeft: 18,
87+
width: 240,
88+
fontSize: 24,
89+
}}
90+
/>
91+
</Box>
92+
) : data?.contact ? (
93+
<>
94+
<PrimaryContactName role="ContactName" variant="h5">
95+
{`${data.contact.primaryPerson?.firstName} ${data.contact.primaryPerson?.lastName}`}
96+
</PrimaryContactName>
97+
<PrimaryText variant="subtitle1">{` - ${t(
98+
'Primary',
99+
)}`}</PrimaryText>
100+
</>
101+
) : null}
102+
</HeaderBarContactWrap>
103+
<HeaderBarButtonsWrap>
104+
<ButtonWrap>
105+
<StarContactIcon hasStar={false} />
106+
</ButtonWrap>
107+
<ButtonWrap>
108+
<MoreButtonIcon />
109+
</ButtonWrap>
110+
<ButtonWrap>
111+
<CloseButtonIcon />
112+
</ButtonWrap>
113+
</HeaderBarButtonsWrap>
114+
</HeaderBar>
115+
<HeaderSectionWrap>
116+
<Box flex={1}>
117+
<ContactHeaderAddressSection
118+
loading={loading}
119+
contact={data?.contact}
120+
/>
121+
<ContactHeaderPhoneSection
122+
loading={loading}
123+
contact={data?.contact}
124+
/>
125+
<ContactHeaderEmailSection
126+
loading={loading}
127+
contact={data?.contact}
128+
/>
129+
</Box>
130+
<Box flex={1}>
131+
<ContactHeaderStatusSection
132+
loading={loading}
133+
contact={data?.contact}
134+
/>
135+
</Box>
136+
</HeaderSectionWrap>
27137
</Box>
28138
);
29139
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
fragment ContactHeaderAddress on Contact {
2+
id
3+
greeting
4+
primaryAddress {
5+
street
6+
city
7+
state
8+
postalCode
9+
country
10+
}
11+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React, { ReactElement } from 'react';
2+
import { gqlMock } from '../../../../../../__tests__/util/graphqlMocking';
3+
4+
import {
5+
ContactDetailsHeaderFragment,
6+
ContactDetailsHeaderFragmentDoc,
7+
} from '../ContactDetailsHeader.generated';
8+
import { ContactHeaderAddressSection } from './ContactHeaderAddressSection';
9+
10+
export default {
11+
title: 'Contacts/ContactDetails/Header/AddressSection',
12+
};
13+
14+
export const Default = (): ReactElement => {
15+
const contact = gqlMock<ContactDetailsHeaderFragment>(
16+
ContactDetailsHeaderFragmentDoc,
17+
);
18+
19+
return <ContactHeaderAddressSection loading={false} contact={contact} />;
20+
};
21+
22+
export const Loading = (): ReactElement => {
23+
return <ContactHeaderAddressSection loading={true} contact={null} />;
24+
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { render } from '@testing-library/react';
2+
import React from 'react';
3+
import { gqlMock } from '../../../../../../__tests__/util/graphqlMocking';
4+
import {
5+
ContactDetailsHeaderFragment,
6+
ContactDetailsHeaderFragmentDoc,
7+
} from '../ContactDetailsHeader.generated';
8+
import { ContactHeaderAddressSection } from './ContactHeaderAddressSection';
9+
10+
const contact = gqlMock<ContactDetailsHeaderFragment>(
11+
ContactDetailsHeaderFragmentDoc,
12+
);
13+
14+
describe('ContactHeaderAddressSection', () => {
15+
it('should show loading state', async () => {
16+
const { queryByText } = render(
17+
<ContactHeaderAddressSection loading={true} contact={null} />,
18+
);
19+
20+
expect(queryByText(contact.primaryAddress.street)).toBeNull();
21+
});
22+
23+
it('should render with contact details', async () => {
24+
const { queryByText } = render(
25+
<ContactHeaderAddressSection loading={false} contact={contact} />,
26+
);
27+
28+
expect(queryByText(contact.greeting)).toBeInTheDocument();
29+
expect(queryByText(contact.primaryAddress.street)).toBeInTheDocument();
30+
});
31+
});

0 commit comments

Comments
 (0)