More icons and styling
@ -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}> </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);
|
||||
|
1
src/components/Icon/svg/bolt-white.svg
Normal 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 |
1
src/components/Icon/svg/github/issue-closed.svg
Normal 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 |
1
src/components/Icon/svg/github/issue-open.svg
Normal 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 |
@ -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 |
@ -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 |
1
src/components/Icon/svg/github/pr-closed.svg
Normal 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 |
1
src/components/Icon/svg/github/pr-merged.svg
Normal 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 |
1
src/components/Icon/svg/github/pr-open.svg
Normal 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 |
@ -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 |
@ -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 |
@ -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 |
1
src/components/Icon/svg/inbox-white.svg
Normal 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 |
1
src/components/Icon/svg/people-white.svg
Normal 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 |
1
src/components/Icon/svg/people.svg
Normal 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
@ -0,0 +1,7 @@
|
||||
export const Filters = {
|
||||
ALL: 'all',
|
||||
REVIEW_REQUESTED: 'review_requested',
|
||||
PARTICIPATING: 'participating',
|
||||
SUBSCRIBED: 'subscribed',
|
||||
HOT: 'hot',
|
||||
};
|
@ -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>
|
||||
);
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|