Skip to content

Commit c1ebda3

Browse files
authored
feat: notification user avatar (#915)
* feat: notification user avatar * feat: simplify notification footer format
1 parent 71eb675 commit c1ebda3

13 files changed

+499
-137
lines changed

.husky/pre-commit

100755100644
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
pnpx lint-staged
2-
pnpm test -- --onlyChanged
2+
pnpm test -- --onlyChanged

src/__mocks__/mockedData.ts

+172-41
Large diffs are not rendered by default.

src/components/NotificationRow.test.tsx

+18-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ describe('components/NotificationRow.tsx', () => {
1919
jest.clearAllMocks();
2020
});
2121

22-
it('should render itself & its children', async () => {
23-
(global as any).Date.now = jest.fn(() => new Date('2014'));
22+
it('should render itself & its children with avatar', async () => {
23+
(global as any).Date.now = jest.fn(() => new Date('2024'));
2424

2525
const props = {
2626
notification: mockedSingleNotification,
@@ -31,6 +31,21 @@ describe('components/NotificationRow.tsx', () => {
3131
expect(tree).toMatchSnapshot();
3232
});
3333

34+
it('should render itself & its children without avatar', async () => {
35+
(global as any).Date.now = jest.fn(() => new Date('2024'));
36+
37+
const mockNotification = mockedSingleNotification;
38+
mockNotification.subject.user = null;
39+
40+
const props = {
41+
notification: mockNotification,
42+
hostname: 'github.com',
43+
};
44+
45+
const tree = TestRenderer.create(<NotificationRow {...props} />);
46+
expect(tree).toMatchSnapshot();
47+
});
48+
3449
it('should open a notification in the browser', () => {
3550
const removeNotificationFromState = jest.fn();
3651

@@ -159,6 +174,7 @@ describe('components/NotificationRow.tsx', () => {
159174
user: {
160175
login: 'some-user',
161176
html_url: 'https://github.com/some-user',
177+
avatar_url: 'https://avatars.githubusercontent.com/u/123456789?v=4',
162178
type: 'User',
163179
},
164180
},

src/components/NotificationRow.tsx

+31-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import React, { useCallback, useContext } from 'react';
22
import { formatDistanceToNow, parseISO } from 'date-fns';
3-
import { CheckIcon, BellSlashIcon, ReadIcon } from '@primer/octicons-react';
3+
import {
4+
CheckIcon,
5+
BellSlashIcon,
6+
ReadIcon,
7+
FeedPersonIcon,
8+
} from '@primer/octicons-react';
49

510
import {
611
formatReason,
@@ -69,10 +74,10 @@ export const NotificationRow: React.FC<IProps> = ({
6974
const updatedAt = formatDistanceToNow(parseISO(notification.updated_at), {
7075
addSuffix: true,
7176
});
72-
const updatedBy = notification.subject.user
73-
? ` by ${notification.subject.user.login}`
74-
: '';
75-
const updatedLabel = `Updated ${updatedAt}${updatedBy}`;
77+
78+
const updatedLabel = notification.subject.user
79+
? `${notification.subject.user.login} updated ${updatedAt}`
80+
: `Updated ${updatedAt}`;
7681
const notificationTitle = formatForDisplay([
7782
notification.subject.state,
7883
notification.subject.type,
@@ -97,22 +102,32 @@ export const NotificationRow: React.FC<IProps> = ({
97102
</div>
98103

99104
<div className="text-xs text-capitalize whitespace-nowrap overflow-ellipsis overflow-hidden">
100-
<span title={reason.description}>{reason.type}</span> -{' '}
101-
<span title={updatedLabel}>
102-
Updated {updatedAt}
103-
{notification.subject.user && (
104-
<>
105-
{' '}
106-
by{' '}
105+
<span className="flex items-center">
106+
<span title={updatedLabel} className="flex">
107+
{notification.subject.user ? (
107108
<span
108-
className="cursor-pointer"
109109
title="View User Profile"
110110
onClick={openUserProfile}
111111
>
112-
{notification.subject.user.login}
112+
<img
113+
className="rounded-full w-4 h-4 cursor-pointer"
114+
src={notification.subject.user.avatar_url}
115+
title={notification.subject.user.login}
116+
/>
117+
</span>
118+
) : (
119+
<span>
120+
<FeedPersonIcon
121+
size={16}
122+
className="text-gray-500 dark:text-gray-300"
123+
/>
113124
</span>
114-
</>
115-
)}
125+
)}
126+
<span className="ml-1" title={reason.description}>
127+
{reason.type}
128+
</span>
129+
<span className="ml-1">{updatedAt}</span>
130+
</span>
116131
</span>
117132
</div>
118133
</div>

src/components/__snapshots__/NotificationRow.test.tsx.snap

+209-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`components/NotificationRow.tsx should render itself & its children 1`] = `
3+
exports[`components/NotificationRow.tsx should render itself & its children with avatar 1`] = `
44
<div
55
className="flex space-x-3 py-2 px-3 bg-white dark:bg-gray-dark dark:text-white hover:bg-gray-100 dark:hover:bg-gray-darker border-b border-gray-100 dark:border-gray-darker group"
66
>
@@ -49,17 +49,218 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
4949
className="text-xs text-capitalize whitespace-nowrap overflow-ellipsis overflow-hidden"
5050
>
5151
<span
52-
title="You're watching the repository."
52+
className="flex items-center"
5353
>
54-
Subscribed
54+
<span
55+
className="flex"
56+
title="manosim updated over 6 years ago"
57+
>
58+
<span
59+
onClick={[Function]}
60+
title="View User Profile"
61+
>
62+
<img
63+
className="rounded-full w-4 h-4 cursor-pointer"
64+
src="https://avatars0.githubusercontent.com/u/6333409?v=3"
65+
title="manosim"
66+
/>
67+
</span>
68+
<span
69+
className="ml-1"
70+
title="You're watching the repository."
71+
>
72+
Updated
73+
</span>
74+
<span
75+
className="ml-1"
76+
>
77+
over 6 years ago
78+
</span>
79+
</span>
5580
</span>
56-
-
57-
81+
</div>
82+
</div>
83+
<div
84+
className="flex justify-center items-center gap-2 opacity-0 group-hover:opacity-80 transition-opacity"
85+
>
86+
<button
87+
className="focus:outline-none h-full hover:text-green-500"
88+
onClick={[Function]}
89+
title="Mark as Done"
90+
>
91+
<svg
92+
aria-label="Mark as Done"
93+
className="octicon octicon-check"
94+
fill="currentColor"
95+
focusable="false"
96+
height={16}
97+
role="img"
98+
style={
99+
{
100+
"display": "inline-block",
101+
"overflow": "visible",
102+
"userSelect": "none",
103+
"verticalAlign": "text-bottom",
104+
}
105+
}
106+
viewBox="0 0 16 16"
107+
width={16}
108+
>
109+
<path
110+
d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
111+
/>
112+
</svg>
113+
</button>
114+
<button
115+
className="focus:outline-none h-full hover:text-red-500"
116+
onClick={[Function]}
117+
title="Unsubscribe"
118+
>
119+
<svg
120+
aria-label="Unsubscribe"
121+
className="octicon octicon-bell-slash"
122+
fill="currentColor"
123+
focusable="false"
124+
height={14}
125+
role="img"
126+
style={
127+
{
128+
"display": "inline-block",
129+
"overflow": "visible",
130+
"userSelect": "none",
131+
"verticalAlign": "text-bottom",
132+
}
133+
}
134+
viewBox="0 0 16 16"
135+
width={14}
136+
>
137+
<path
138+
d="m4.182 4.31.016.011 10.104 7.316.013.01 1.375.996a.75.75 0 1 1-.88 1.214L13.626 13H2.518a1.516 1.516 0 0 1-1.263-2.36l1.703-2.554A.255.255 0 0 0 3 7.947V5.305L.31 3.357a.75.75 0 1 1 .88-1.214Zm7.373 7.19L4.5 6.391v1.556c0 .346-.102.683-.294.97l-1.703 2.556a.017.017 0 0 0-.003.01c0 .005.002.009.005.012l.006.004.007.001ZM8 1.5c-.997 0-1.895.416-2.534 1.086A.75.75 0 1 1 4.38 1.55 5 5 0 0 1 13 5v2.373a.75.75 0 0 1-1.5 0V5A3.5 3.5 0 0 0 8 1.5ZM8 16a2 2 0 0 1-1.985-1.75c-.017-.137.097-.25.235-.25h3.5c.138 0 .252.113.235.25A2 2 0 0 1 8 16Z"
139+
/>
140+
</svg>
141+
</button>
142+
<button
143+
className="focus:outline-none h-full hover:text-green-500"
144+
onClick={[Function]}
145+
title="Mark as Read"
146+
>
147+
<svg
148+
aria-label="Mark as Read"
149+
className="octicon octicon-read"
150+
fill="currentColor"
151+
focusable="false"
152+
height={14}
153+
role="img"
154+
style={
155+
{
156+
"display": "inline-block",
157+
"overflow": "visible",
158+
"userSelect": "none",
159+
"verticalAlign": "text-bottom",
160+
}
161+
}
162+
viewBox="0 0 16 16"
163+
width={14}
164+
>
165+
<path
166+
d="M7.115.65a1.752 1.752 0 0 1 1.77 0l6.25 3.663c.536.314.865.889.865 1.51v6.427A1.75 1.75 0 0 1 14.25 14H1.75A1.75 1.75 0 0 1 0 12.25V5.823c0-.621.33-1.196.865-1.51Zm1.011 1.293a.252.252 0 0 0-.252 0l-5.72 3.353L6.468 7.76a2.748 2.748 0 0 1 3.066 0l4.312-2.464-5.719-3.353ZM13.15 12.5 8.772 9.06a1.25 1.25 0 0 0-1.544 0L2.85 12.5Zm1.35-5.85-3.687 2.106 3.687 2.897ZM5.187 8.756 1.5 6.65v5.003Z"
167+
/>
168+
</svg>
169+
</button>
170+
</div>
171+
</div>
172+
`;
173+
174+
exports[`components/NotificationRow.tsx should render itself & its children without avatar 1`] = `
175+
<div
176+
className="flex space-x-3 py-2 px-3 bg-white dark:bg-gray-dark dark:text-white hover:bg-gray-100 dark:hover:bg-gray-darker border-b border-gray-100 dark:border-gray-darker group"
177+
>
178+
<div
179+
className="flex justify-center items-center w-5 text-green-500"
180+
title="Open Issue"
181+
>
182+
<svg
183+
aria-label="Issue"
184+
className="octicon octicon-issue-opened"
185+
fill="currentColor"
186+
focusable="false"
187+
height={18}
188+
role="img"
189+
style={
190+
{
191+
"display": "inline-block",
192+
"overflow": "visible",
193+
"userSelect": "none",
194+
"verticalAlign": "text-bottom",
195+
}
196+
}
197+
viewBox="0 0 16 16"
198+
width={18}
199+
>
200+
<path
201+
d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"
202+
/>
203+
<path
204+
d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"
205+
/>
206+
</svg>
207+
</div>
208+
<div
209+
className="flex-1 overflow-hidden"
210+
>
211+
<div
212+
className="mb-1 text-sm whitespace-nowrap overflow-ellipsis overflow-hidden cursor-pointer"
213+
onClick={[Function]}
214+
role="main"
215+
title="I am a robot and this is a test!"
216+
>
217+
I am a robot and this is a test!
218+
</div>
219+
<div
220+
className="text-xs text-capitalize whitespace-nowrap overflow-ellipsis overflow-hidden"
221+
>
58222
<span
59-
title="Updated in over 3 years"
223+
className="flex items-center"
60224
>
61-
Updated
62-
in over 3 years
225+
<span
226+
className="flex"
227+
title="Updated over 6 years ago"
228+
>
229+
<span>
230+
<svg
231+
aria-hidden="true"
232+
className="text-gray-500 dark:text-gray-300"
233+
fill="currentColor"
234+
focusable="false"
235+
height={16}
236+
style={
237+
{
238+
"display": "inline-block",
239+
"overflow": "visible",
240+
"userSelect": "none",
241+
"verticalAlign": "text-bottom",
242+
}
243+
}
244+
viewBox="0 0 16 16"
245+
width={16}
246+
>
247+
<path
248+
d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16Zm.847-8.145a2.502 2.502 0 1 0-1.694 0C5.471 8.261 4 9.775 4 11c0 .395.145.995 1 .995h6c.855 0 1-.6 1-.995 0-1.224-1.47-2.74-3.153-3.145Z"
249+
/>
250+
</svg>
251+
</span>
252+
<span
253+
className="ml-1"
254+
title="You're watching the repository."
255+
>
256+
Updated
257+
</span>
258+
<span
259+
className="ml-1"
260+
>
261+
over 6 years ago
262+
</span>
263+
</span>
63264
</span>
64265
</div>
65266
</div>

0 commit comments

Comments
 (0)