More icons and styling

This commit is contained in:
Nicholas Zuber 2018-11-01 16:46:49 -04:00
parent 9ba0b4f04b
commit 698d9d44a2
19 changed files with 310 additions and 56 deletions

View File

@ -4,6 +4,7 @@ import styled from 'react-emotion';
import allInbox from './svg/all_inbox.svg';
import back from './svg/back.svg';
import bolt from './svg/bolt.svg';
import boltWhite from './svg/bolt-white.svg';
import bookmarkAlt from './svg/bookmark-alt.svg';
import bookmark from './svg/bookmark.svg';
import bookmarks from './svg/bookmarks.svg';
@ -12,9 +13,12 @@ import doneAll from './svg/done-all.svg';
import done from './svg/done.svg';
import hot from './svg/hot.svg';
import inbox from './svg/inbox.svg';
import inboxWhite from './svg/inbox-white.svg';
import locked from './svg/locked.svg';
import menu from './svg/menu.svg';
import next from './svg/next.svg';
import people from './svg/people.svg';
import peopleWhite from './svg/people-white.svg';
import refresh from './svg/refresh.svg';
import search from './svg/search.svg';
import settings from './svg/settings.svg';
@ -23,19 +27,29 @@ import star from './svg/star.svg';
import unlocked from './svg/unlocked.svg';
import x from './svg/x.svg';
import issue_closed from './svg/github/issue-closed.svg';
import issue_open from './svg/github/issue-open.svg';
import pr_closed from './svg/github/pr-closed.svg';
import pr_open from './svg/github/pr-open.svg';
import pr_merged from './svg/github/pr-merged.svg';
const SvgIcon = styled('div')({
position: 'relative',
backgroundSize: 'cover'
}, ({size, icon, opacity}) => ({
backgroundSize: 'cover',
fontSize: 14,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}, ({size, icon, opacity, shrink}) => ({
height: size || 24,
width: size || 24,
background: `url(${icon}) center center no-repeat`,
opacity
opacity,
transform: shrink ? `scale(${shrink})` : 'inherit'
}));
export default function Icon ({src, ...props}) {
return <SvgIcon {...props} icon={src} />
return <SvgIcon {...props} icon={src}>&nbsp;</SvgIcon>
}
const createIcon = src => props => <Icon {...props} src={src} />;
@ -43,6 +57,7 @@ const createIcon = src => props => <Icon {...props} src={src} />;
Icon.AllInbox = createIcon(allInbox);
Icon.Back = createIcon(back);
Icon.Bolt = createIcon(bolt);
Icon.BoltWhite = createIcon(boltWhite);
Icon.BookmarkAlt = createIcon(bookmarkAlt);
Icon.Bookmark = createIcon(bookmark);
Icon.Bookmarks = createIcon(bookmarks);
@ -51,9 +66,12 @@ Icon.DoneAll = createIcon(doneAll);
Icon.Done = createIcon(done);
Icon.Hot = createIcon(hot);
Icon.Inbox = createIcon(inbox);
Icon.InboxWhite = createIcon(inboxWhite);
Icon.Locked = createIcon(locked);
Icon.Menu = createIcon(menu);
Icon.Next = createIcon(next);
Icon.People = createIcon(people);
Icon.PeopleWhite = createIcon(peopleWhite);
Icon.Refresh = createIcon(refresh);
Icon.Search = createIcon(search);
Icon.Settings = createIcon(settings);
@ -61,3 +79,9 @@ Icon.StarAlt = createIcon(starAlt);
Icon.Star = createIcon(star);
Icon.Unlocked = createIcon(unlocked);
Icon.X = createIcon(x);
Icon.IssueClosed = createIcon(issue_closed);
Icon.IssueOpen = createIcon(issue_open);
Icon.PrClosed = createIcon(pr_closed);
Icon.PrOpen = createIcon(pr_open);
Icon.PrMerged = createIcon(pr_merged);

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path fill="#fff" d="M12 2.02c-5.51 0-9.98 4.47-9.98 9.98s4.47 9.98 9.98 9.98 9.98-4.47 9.98-9.98S17.51 2.02 12 2.02zM11.48 20v-6.26H8L13 4v6.26h3.35L11.48 20z"/></svg>

After

Width:  |  Height:  |  Size: 289 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" version="1.1" width="16" height="16"><path fill="#ee3044" fill-rule="evenodd" d="M7 10h2v2H7v-2zm2-6H7v5h2V4zm1.5 1.5l-1 1L12 9l4-4.5-1-1L12 7l-1.5-1.5zM8 13.7A5.71 5.71 0 0 1 2.3 8c0-3.14 2.56-5.7 5.7-5.7 1.83 0 3.45.88 4.5 2.2l.92-.92A6.947 6.947 0 0 0 8 1C4.14 1 1 4.14 1 8s3.14 7 7 7 7-3.14 7-7l-1.52 1.52c-.66 2.41-2.86 4.19-5.48 4.19v-.01z"></path></svg>

After

Width:  |  Height:  |  Size: 421 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 16" version="1.1" width="14" height="16" aria-hidden="true"><path fill="#2cbf73" fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg>

After

Width:  |  Height:  |  Size: 354 B

View File

@ -1 +0,0 @@
<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7 10h2v2H7v-2zm2-6H7v5h2V4zm1.5 1.5l-1 1L12 9l4-4.5-1-1L12 7l-1.5-1.5zM8 13.7A5.71 5.71 0 0 1 2.3 8c0-3.14 2.56-5.7 5.7-5.7 1.83 0 3.45.88 4.5 2.2l.92-.92A6.947 6.947 0 0 0 8 1C4.14 1 1 4.14 1 8s3.14 7 7 7 7-3.14 7-7l-1.52 1.52c-.66 2.41-2.86 4.19-5.48 4.19v-.01z"></path></svg>

Before

Width:  |  Height:  |  Size: 390 B

View File

@ -1 +0,0 @@
<svg viewBox="0 0 14 16" version="1.1" width="14" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg>

Before

Width:  |  Height:  |  Size: 304 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill="#ee3044" fill-rule="evenodd" d="M11 11.28V5c-.03-.78-.34-1.47-.94-2.06C9.46 2.35 8.78 2.03 8 2H7V0L4 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 10 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zM4 3c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v6.56A1.993 1.993 0 0 0 2 15a1.993 1.993 0 0 0 1-3.72V4.72c.59-.34 1-.98 1-1.72zm-.8 10c0 .66-.55 1.2-1.2 1.2-.65 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg>

After

Width:  |  Height:  |  Size: 748 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M11 11.28V5c-.03-.78-.34-1.47-.94-2.06C9.46 2.35 8.78 2.03 8 2H7V0L4 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 10 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zM4 3c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v6.56A1.993 1.993 0 0 0 2 15a1.993 1.993 0 0 0 1-3.72V4.72c.59-.34 1-.98 1-1.72zm-.8 10c0 .66-.55 1.2-1.2 1.2-.65 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg>

After

Width:  |  Height:  |  Size: 733 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill="#2cbf73" fill-rule="evenodd" d="M11 11.28V5c-.03-.78-.34-1.47-.94-2.06C9.46 2.35 8.78 2.03 8 2H7V0L4 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 10 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zM4 3c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v6.56A1.993 1.993 0 0 0 2 15a1.993 1.993 0 0 0 1-3.72V4.72c.59-.34 1-.98 1-1.72zm-.8 10c0 .66-.55 1.2-1.2 1.2-.65 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg>

After

Width:  |  Height:  |  Size: 748 B

View File

@ -1 +0,0 @@
<svg viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M11 11.28V5c-.03-.78-.34-1.47-.94-2.06C9.46 2.35 8.78 2.03 8 2H7V0L4 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 10 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zM4 3c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v6.56A1.993 1.993 0 0 0 2 15a1.993 1.993 0 0 0 1-3.72V4.72c.59-.34 1-.98 1-1.72zm-.8 10c0 .66-.55 1.2-1.2 1.2-.65 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg>

Before

Width:  |  Height:  |  Size: 698 B

View File

@ -1 +0,0 @@
<svg viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M11 11.28V5c-.03-.78-.34-1.47-.94-2.06C9.46 2.35 8.78 2.03 8 2H7V0L4 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 10 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zM4 3c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v6.56A1.993 1.993 0 0 0 2 15a1.993 1.993 0 0 0 1-3.72V4.72c.59-.34 1-.98 1-1.72zm-.8 10c0 .66-.55 1.2-1.2 1.2-.65 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg>

Before

Width:  |  Height:  |  Size: 698 B

View File

@ -1 +0,0 @@
<svg viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M11 11.28V5c-.03-.78-.34-1.47-.94-2.06C9.46 2.35 8.78 2.03 8 2H7V0L4 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 10 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zM4 3c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v6.56A1.993 1.993 0 0 0 2 15a1.993 1.993 0 0 0 1-3.72V4.72c.59-.34 1-.98 1-1.72zm-.8 10c0 .66-.55 1.2-1.2 1.2-.65 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg>

Before

Width:  |  Height:  |  Size: 698 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M19 3H4.99c-1.11 0-1.98.89-1.98 2L3 19c0 1.1.88 2 1.99 2H19c1.1 0 2-.9 2-2V5c0-1.11-.9-2-2-2zm0 12h-4c0 1.66-1.35 3-3 3s-3-1.34-3-3H4.99V5H19v10z"/><path fill="none" d="M0 0h24v24H0V0z"/></svg>

After

Width:  |  Height:  |  Size: 298 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path fill="#fff" d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>

After

Width:  |  Height:  |  Size: 440 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>

After

Width:  |  Height:  |  Size: 427 B

7
src/constants/filters.js Normal file
View File

@ -0,0 +1,7 @@
export const Filters = {
ALL: 'all',
REVIEW_REQUESTED: 'review_requested',
PARTICIPATING: 'participating',
SUBSCRIBED: 'subscribed',
HOT: 'hot',
};

View File

@ -6,6 +6,7 @@ import Icon from '../../components/Icon';
import Logo from '../../components/Logo';
import LoadingIcon from '../../components/LoadingIcon';
import { routes } from '../../constants';
import { Filters } from '../../constants/filters';
import { withOnEnter } from '../../enhance';
import '../../styles/gradient.css';
@ -18,7 +19,9 @@ const NotificationsContainer = styled('div')({
width: '100%',
height: '100vh',
display: 'flex',
flexDirection: 'row'
flexDirection: 'row',
overflowX: 'hidden',
boxSizing: 'border-box'
});
const NavigationContainer = styled('div')({
@ -43,10 +46,34 @@ const GeneralOptionsContainer = styled(NavigationContainer)({
});
const Sidebar = styled('div')({
flex: '0 0 75px',
marginTop: 15
flex: '0 0 180px',
padding: '0 20px 20px',
marginTop: 15,
'a': {
textAlign: 'left',
margin: '0 auto',
cursor: 'pointer',
borderRadius: '8px',
alignItems: 'center',
padding: '0 16px',
height: '48px',
fontSize: '12px',
fontWeight: '700',
textTransform: 'uppercase',
textDecoration: 'none',
transition: 'background 0.12s ease-in-out',
display: 'flex'
}
});
const SidebarLink = styled('a')({}, ({active, color}) => ({
background: active ? color : 'none',
color: active ? '#fff' : '#1a1a1a',
':hover': {
background: active ? color: 'rgba(200, 200, 200, .25)'
}
}));
const Notifications = styled('div')({
flex: 1,
});
@ -90,8 +117,11 @@ const SearchField = styled('div')({
height: '48px',
fontSize: '14px',
textDecoration: 'none',
transition: 'all 0.12s ease-in-out',
display: 'inline-flex'
transition: 'all 0.06s ease-in-out',
display: 'inline-flex',
':focus-within': {
boxShadow: '0 3px 9px #4a4a4a5c',
}
});
const LoaderContainer = styled('div')({
@ -118,29 +148,98 @@ const SearchInput = styled('input')({
const EnhancedSearchInput = withOnEnter(SearchInput);
const NotificationRow = styled('div')({
cursor: 'pointer',
display: 'block',
textAlign: 'left',
width: '100%',
margin: '0 auto',
background: '#fff',
padding: '8px 16px',
padding: '16px 24px',
transition: 'all 0.12s ease-in-out',
borderRadius: 8,
boxSizing: 'border-box',
':hover': {
background: '#f9f9f9'
}
});
const NotificationTitle = styled('div')({
position: 'relative',
}, ({img}) => img && ({
paddingLeft: 20,
'::before': {
content: "''",
position: 'absolute',
display: 'block',
background: `url(${img}) center center no-repeat`,
backgroundSize: 'cover',
left: 0,
height: 20,
width: 20,
}
}));
const Repository = styled('span')({
fontWeight: 600,
marginLeft: 10,
fontSize: 16
});
const PRIssue = styled(Repository)({
fontWeight: 400,
});
function getPRIssueIcon (type, reason) {
const grow = 1.2;
switch (type) {
case 'PullRequest':
return (
<Icon.PrOpen shrink={grow} />
);
case 'Issue':
return (
<Icon.IssueOpen shrink={grow} />
);
default:
return null;
}
}
export default function Scene ({
notifications,
onLogout,
onSearch,
onMarkAsRead,
onFetchNotifications,
isSearching,
isFetchingNotifications,
fetchingNotificationsError,
activeFilter,
onSetActiveFilter,
}) {
const isLoading = isSearching || isFetchingNotifications;
let filterMethod = () => true;
switch (activeFilter) {
case Filters.REVIEW_REQUESTED:
filterMethod = n => n.reason === 'review_requested';
break;
case Filters.PARTICIPATING:
filterMethod = n => (
n.reason !== 'subscribed' &&
n.reason !== 'manual' &&
n.reason !== 'invitation'
);
break;
case Filters.ALL:
default:
filterMethod = () => true;
}
notifications = notifications
.sort((a, b) => b.repository.name.localeCompare(a.repository.name))
.filter(n => console.warn(n.reason === 'review_requested') || n.reason === 'review_requested');
.filter(filterMethod);
return (
<div className="container-gradient" style={{
@ -177,24 +276,83 @@ export default function Scene ({
<Tab disabled={isLoading}>
<Icon.Refresh
opacity={0.9}
onClick={onFetchNotifications}
onClick={!isLoading && onFetchNotifications}
/>
</Tab>
<Tab disabled={isLoading}>
<Icon.DoneAll
opacity={0.9}
onClick={onFetchNotifications}
onClick={!isLoading && (() => onMarkAsRead('402658026'))}
/>
</Tab>
<Tab>
<div style={{
position: 'relative',
height: 24,
width: 24,
fontSize: 14,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}>
{notifications.length}
</div>
</Tab>
</GeneralOptionsContainer>
<NotificationsContainer>
<Sidebar>
<Tab disabled={isLoading}>
<Icon.Refresh
opacity={0.9}
onClick={onFetchNotifications}
/>
</Tab>
<SidebarLink
active={activeFilter === Filters.ALL}
color="#9065ff"
onClick={() => onSetActiveFilter(Filters.ALL)}
>
{activeFilter === Filters.ALL ? (
<Icon.InboxWhite shrink={.6} />
) : (
<Icon.Inbox shrink={.6} />
)}
all notifications
</SidebarLink>
<SidebarLink
active={activeFilter === Filters.REVIEW_REQUESTED}
color="#2cbf73"
onClick={() => onSetActiveFilter(Filters.REVIEW_REQUESTED)}
>
{activeFilter === Filters.REVIEW_REQUESTED ? (
<Icon.BoltWhite shrink={.6} />
) : (
<Icon.Bolt shrink={.6} />
)}
review requested
</SidebarLink>
<SidebarLink
active={activeFilter === Filters.PARTICIPATING}
color="#00A0F5"
onClick={() => onSetActiveFilter(Filters.PARTICIPATING)}
>
{activeFilter === Filters.PARTICIPATING ? (
<Icon.PeopleWhite shrink={.6} />
) : (
<Icon.People shrink={.6} />
)}
participating
</SidebarLink>
<SidebarLink
active={activeFilter === Filters.SUBSCRIBED}
color="#f68700"
onClick={() => onSetActiveFilter(Filters.SUBSCRIBED)}
>
<Icon.Bookmark shrink={.6} />
subscribed
</SidebarLink>
<SidebarLink
active={activeFilter === Filters.HOT}
color="#ee3044"
onClick={() => onSetActiveFilter(Filters.HOT)}
>
<Icon.Hot shrink={.6} />
hot
</SidebarLink>
</Sidebar>
<Notifications>
{isFetchingNotifications ? (
@ -209,16 +367,30 @@ export default function Scene ({
<div>
{notifications.map(n => (
<NotificationRow key={n.id}>
<img width={16} src={n.repository.owner.avatar_url} />
<p style={{fontWeight: 'bold'}}>{n.repository.name}</p>
<p>{n.subject.title} ({n.subject.type}, {n.reason})</p>
<p>Last read at {n.last_read_at ? moment(n.last_read_at).format('dddd h:mma') : 'never'}</p>
<p>Last updated at {moment(n.last_updated).format('dddd h:mma')}</p>
<NotificationTitle>
<div style={{
float: 'left',
marginTop: -3
}}>
{getPRIssueIcon(n.subject.type, n.reason)}
</div>
<PRIssue>{n.subject.title} ({n.reason})</PRIssue>
{/* <Repository>{n.repository.name}</Repository> */}
</NotificationTitle>
{/* <p>Last read at {n.last_read_at ? moment(n.last_read_at).format('dddd h:mma') : 'never'}</p>
<p>Last updated at {moment(n.last_updated).format('dddd h:mma')}</p> */}
</NotificationRow>
))}
</div>
)}
</Notifications>
<Sidebar>
<SidebarLink
active={activeFilter === Filters.HOT}
color="#ee3044"
onClick={() => onSetActiveFilter(Filters.HOT)}
>hot</SidebarLink>
</Sidebar>
</NotificationsContainer>
</div>
);

View File

@ -6,11 +6,17 @@ import { withAuthProvider } from '../../providers/Auth';
import { withCookiesProvider } from '../../providers/Cookies';
import { OAUTH_TOKEN_COOKIE } from '../../constants/cookies';
import { routes } from '../../constants';
import { Filters } from '../../constants/filters';
import Scene from './Scene';
class NotificationsPage extends React.Component {
state = {
isSearching: false
isSearching: false,
activeFilter: Filters.ALL
}
onSetActiveFilter = filter => {
this.setState({ activeFilter: filter });
}
onLogout = () => {
@ -41,6 +47,7 @@ class NotificationsPage extends React.Component {
const {
fetchNotifications,
markAsRead,
notifications,
loading: isFetchingNotifications,
error: fetchingNotificationsError,
@ -52,9 +59,12 @@ class NotificationsPage extends React.Component {
onLogout={this.onLogout}
onSearch={this.onSearch}
onFetchNotifications={fetchNotifications}
onMarkAsRead={markAsRead}
isSearching={this.state.isSearching}
isFetchingNotifications={isFetchingNotifications}
fetchingNotificationsError={fetchingNotificationsError}
onSetActiveFilter={this.onSetActiveFilter}
activeFilter={this.state.activeFilter}
/>
);
}

View File

@ -4,6 +4,34 @@ import {MockNotifications} from '../utils/mocks';
const BASE_GITHUB_API_URL = 'https://api.github.com';
function processHeadersAndBodyJson (response) {
const entries = response.headers.entries();
const headers = {};
for (let [name, value] of entries) {
headers[name] = value;
}
const rawLinks = headers['link'];
const links = {};
if (rawLinks) {
rawLinks.split(',').forEach((p) => {
const section = p.split(';');
if (section.length !== 2) {
throw new Error("section could not be split on ';'");
}
const url = section[0].replace(/<(.*)>/, '$1').trim();
const page = section[0].match(/page=(\d)/)[1];
const name = section[1].replace(/rel="(.*)"/, '$1').trim();
links[name] = {url, page};
})
}
// links.next.page
headers['link'] = links;
return [headers, response.json()];
}
class NotificationsProvider extends React.Component {
constructor (props) {
super(props);
@ -31,36 +59,14 @@ class NotificationsProvider extends React.Component {
method: 'GET',
headers: headers
})
.then(response => {
const entries = response.headers.entries();
const headers = {};
for (let [name, value] of entries) {
headers[name] = value;
}
.then(processHeadersAndBodyJson)
.then(([headers, body]) => {
// If there were updates, make sure we get the newest last-modified.
if (headers['last-modified']) {
this.last_modified = headers['last-modified'];
}
const rawLinks = headers['link'];
const links = {};
if (rawLinks) {
rawLinks.split(',').forEach((p) => {
const section = p.split(';');
if (section.length !== 2) {
throw new Error("section could not be split on ';'");
}
const url = section[0].replace(/<(.*)>/, '$1').trim();
const page = section[0].match(/page=(\d)/)[1];
const name = section[1].replace(/rel="(.*)"/, '$1').trim();
links[name] = {url, page};
})
}
// links.next.page
return response.json();
return body;
});
}
@ -91,10 +97,41 @@ class NotificationsProvider extends React.Component {
});
}
requestMarkAsRead = thread_id => {
const headers = {
'Authorization': `token ${this.props.token}`,
'Content-Type': 'application/json',
};
return fetch(`${BASE_GITHUB_API_URL}/notifications/threads/${thread_id}`, {
method: 'PATCH',
headers: headers
})
.then(processHeadersAndBodyJson)
.then(([headers, body]) => {
console.warn(body)
return body;
});
}
markAsRead = thread_id => {
if (!this.props.token) {
console.error('Unauthenitcated, aborting request.')
return false;
}
this.setState({ loading: true });
return this.requestMarkAsRead(thread_id)
.then(response => console.warn('response', response))
.catch(error => this.setState({ error }))
.finally(() => this.setState({ loading: false }));
}
render () {
return this.props.children({
...this.state,
fetchNotifications: this.fetchNotifications
fetchNotifications: this.fetchNotifications,
markAsRead: this.markAsRead
});
}
}