diff --git a/src/components/Logo/icon-black.png b/src/components/Logo/icon-black.png
new file mode 100644
index 0000000..f0c89fe
Binary files /dev/null and b/src/components/Logo/icon-black.png differ
diff --git a/src/components/Logo/index.js b/src/components/Logo/index.js
index 9f7de46..21bf3f7 100644
--- a/src/components/Logo/index.js
+++ b/src/components/Logo/index.js
@@ -1,5 +1,5 @@
import React from 'react';
-import meteoriteLogo from './logo-white.png';
+import meteoriteLogo from './icon-black.png';
export default function LoadingIcon ({ style, size, ...props }) {
return (
diff --git a/src/pages/Notifications/Scene.js b/src/pages/Notifications/Scene.js
index acfaa22..e24d7e1 100644
--- a/src/pages/Notifications/Scene.js
+++ b/src/pages/Notifications/Scene.js
@@ -677,632 +677,616 @@ function getPRIssueIcon (type, _reasons) {
}
export default function Scene ({
- currentTime,
- stagedStatistics,
- isFirstTimeUser,
- notificationsPermission,
- queuedCount,
- stagedCount,
- closedCount,
- first,
- last,
- lastPage,
- page,
- notifications,
- query,
- activeStatus,
- allNotificationsCount,
- stagedTodayCount,
- onChangePage,
- onSetActiveStatus,
- onClearQuery,
- onLogout,
- onSearch,
- onMarkAsRead,
- onMarkAllAsStaged,
- onFetchNotifications,
- onStageThread,
- onRestoreThread,
- isSearching,
- isFetchingNotifications,
- onClearCache,
- fetchingNotificationsError,
- activeFilter,
- onSetActiveFilter,
- setNotificationsPermission,
- highestScore,
- lowestScore,
- hasUnread,
+ // currentTime,
+ // stagedStatistics,
+ // isFirstTimeUser,
+ // notificationsPermission,
+ // queuedCount,
+ // stagedCount,
+ // closedCount,
+ // first,
+ // last,
+ // lastPage,
+ // page,
+ // notifications,
+ // query,
+ // activeStatus,
+ // allNotificationsCount,
+ // stagedTodayCount,
+ // onChangePage,
+ // onSetActiveStatus,
+ // onClearQuery,
+ // onLogout,
+ // onSearch,
+ // onMarkAsRead,
+ // onMarkAllAsStaged,
+ // onFetchNotifications,
+ // onStageThread,
+ // onRestoreThread,
+ // isSearching,
+ // isFetchingNotifications,
+ // onClearCache,
+ // fetchingNotificationsError,
+ // activeFilter,
+ // onSetActiveFilter,
+ // setNotificationsPermission,
+ // highestScore,
+ // lowestScore,
+ // hasUnread,
...props
}) {
- const loading = isSearching || isFetchingNotifications;
- const isFirstPage = page === 1;
- const isLastPage = page === lastPage;
+ const loading = props.isSearching || props.isFetchingNotifications;
+ const isFirstPage = props.page === 1;
+ const isLastPage = props.page === props.lastPage;
- const NotificationsIcon = notificationsPermission === 'granted'
- ? Icon.NotificationsOn
- : Icon.NotificationsOff;
+ // const NotificationsIcon = notificationsPermission === 'granted'
+ // ? Icon.NotificationsOn
+ // : Icon.NotificationsOff;
- if (isFirstTimeUser && notifications.length > 10) {
- // probably prompt to mark all as read to start out since they prob don't use notifs
- }
+ // if (isFirstTimeUser && notifications.length > 10) {
+ // // probably prompt to mark all as read to start out since they prob don't use notifs
+ // }
- stagedStatistics = stagedStatistics.map(n => parseInt(n, 10));
+ // stagedStatistics = stagedStatistics.map(n => parseInt(n, 10));
- const highestStagedCount = stagedStatistics.reduce((n, m) => Math.max(n, m), 0);
- let lastWeekStats = stagedStatistics.slice(0, 7);
- let thisWeekStats = stagedStatistics.slice(7);
+ // const highestStagedCount = stagedStatistics.reduce((n, m) => Math.max(n, m), 0);
+ // let lastWeekStats = stagedStatistics.slice(0, 7);
+ // let thisWeekStats = stagedStatistics.slice(7);
- // Trim off the weekends.
- lastWeekStats = lastWeekStats.slice(1, -1);
- thisWeekStats = thisWeekStats.slice(1, -1);
+ // // Trim off the weekends.
+ // lastWeekStats = lastWeekStats.slice(1, -1);
+ // thisWeekStats = thisWeekStats.slice(1, -1);
return (
);
- return (
-
-
-
-
{
- onSetActiveStatus(Status.QUEUED);
- onSetActiveFilter(Filters.PARTICIPATING);
- }}
- />
- beta
-
-
- event.target.select()}
- type="text"
- placeholder="Search for notifications"
- onEnter={onSearch}
- />
- {isSearching && }
-
-
- home
-
-
-
-
-
-
-
-
-
-
-
- {currentTime.format('h:mma')}
-
- {currentTime.format('dddd, MMMM Do')}
- You've triaged {stagedTodayCount} notifications today
-
- onSetActiveFilter(Filters.PARTICIPATING)}>
- {activeFilter === Filters.PARTICIPATING ? (
-
- ) : (
-
- )}
- all your updates
-
- onSetActiveFilter(Filters.REVIEW_REQUESTED)}>
- {activeFilter === Filters.REVIEW_REQUESTED ? (
-
- ) : (
-
- )}
- review requested
-
- onSetActiveFilter(Filters.ASSIGNED)}>
- {activeFilter === Filters.ASSIGNED ? (
-
- ) : (
-
- )}
- assigned
-
- onSetActiveFilter(Filters.COMMENT)}>
- {activeFilter === Filters.COMMENT ? (
-
- ) : (
-
- )}
- commented
-
-
-
-
- {/* Last week's statistics */}
-
- {lastWeekStats.map((dayStats, i) => (
-
- ))}
-
- {/* This week's ongoing statistics */}
-
- {thisWeekStats.map((dayStats, i) => (
- = i + 1}
- />
- ))}
-
- {/* Wrapper for tooltips */}
- {/*
- {thisWeekStats.map((dayStats, i) => (
-
- ))}
- */}
-
-
-
-
- Report bugs
- Submit feedback
- See source code
-
-
-
-
-
-
-
- onFetchNotifications()) : undefined}
- />
-
-
- {
- const response = window.confirm('Are you sure you want to mark all your notifications as read?');
- if (response) {
- onMarkAllAsStaged();
- }
- }) : undefined}
- />
-
-
- {
- const response = window.confirm('Are you sure you want to clear the cache?');
- if (response) {
- onClearCache();
- }
- }) : undefined}
- />
-
-
- {
- switch(notificationsPermission) {
- case 'granted':
- return setNotificationsPermission('denied');
- case 'denied':
- case 'default':
- default:
- Notification.requestPermission().then(result => {
- return setNotificationsPermission(result);
- });
- }
- }) : undefined}
- />
-
- {query ? (
-
-
-
- onClearQuery()) : undefined}
- />
-
-
- ) : null}
-
-
-
- onChangePage(page - 1)) : undefined}
- />
-
-
- onChangePage(page + 1)) : undefined}
- />
-
-
-
-
- onSetActiveStatus(Status.QUEUED)}
- href="javascript:void(0);">
- Unread
-
- onSetActiveStatus(Status.STAGED)}
- href="javascript:void(0);">
- Read
-
- onSetActiveStatus(Status.CLOSED)}
- href="javascript:void(0);">
- Archived
-
-
-
-
- {isFetchingNotifications ? (
-
-
-
- ) : fetchingNotificationsError ? (
-
-
- An error occurred when fetching notifications.
- onFetchNotifications()} href="#">Try again?
-
-
- ) : notifications.length <= 0 ? (
-
-
- No
- {activeStatus === Status.QUEUED ? (
- ' unread '
- ) : activeStatus === Status.STAGED ? (
- ' read '
- ) : (
- ' archived '
- )}
- notifications
-
-
- 🎉 You're all set here for the moment
-
- ) : (
-
-
-
- {notifications.map(n => (
-
-
-
- {getPRIssueIcon(n.type, n.reasons)}
-
-
- {
- window.open(n.url);
- onStageThread(n.id, n.repository)
- }}>
-
- {n.name}
-
-
- {getRelativeTime(n.updated_at)}
- {n.isAuthor && (
-
- )}
-
-
-
- {getMessageFromReasons(n.reasons, n.type)}
-
-
-
-
- {activeStatus === Status.QUEUED && n.badges.map(badge => {
- switch (badge) {
- case Badges.HOT:
- // lots of `reasons` within short time frame
- return (
-
- );
- case Badges.OLD:
- // old
- return (
-
- );
- case Badges.COMMENTS:
- // lots of `reasons`
- return (
-
- );
- default:
- return null;
- }
- })}
-
-
-
- window.open(n.repositoryUrl)}
- style={{cursor: 'pointer', userSelect: 'none'}}>
- {n.repository}
-
-
-
- +{n.score}
-
- {activeStatus === Status.QUEUED ? (
-
- onStageThread(n.id, n.repository)) : undefined}
- />
-
- ) : (
-
- onRestoreThread(n.id)) : undefined}
- />
-
- )}
- {activeStatus === Status.CLOSED ? (
-
-
-
- ) : (
-
- onMarkAsRead(n.id, n.repository)) : undefined}
- />
-
- )}
-
-
- ))}
-
-
- {!loading && Page {page} out of {lastPage} }
-
- )}
-
-
-
-
-
- );
+ // return (
+ //
+ //
+ //
+ //
{
+ // onSetActiveStatus(Status.QUEUED);
+ // onSetActiveFilter(Filters.PARTICIPATING);
+ // }}
+ // />
+ // beta
+ //
+ //
+ // event.target.select()}
+ // type="text"
+ // placeholder="Search for notifications"
+ // onEnter={onSearch}
+ // />
+ // {isSearching && }
+ //
+ //
+ // home
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ // {currentTime.format('h:mma')}
+ //
+ // {currentTime.format('dddd, MMMM Do')}
+ // You've triaged {stagedTodayCount} notifications today
+ //
+ // onSetActiveFilter(Filters.PARTICIPATING)}>
+ // {activeFilter === Filters.PARTICIPATING ? (
+ //
+ // ) : (
+ //
+ // )}
+ // all your updates
+ //
+ // onSetActiveFilter(Filters.REVIEW_REQUESTED)}>
+ // {activeFilter === Filters.REVIEW_REQUESTED ? (
+ //
+ // ) : (
+ //
+ // )}
+ // review requested
+ //
+ // onSetActiveFilter(Filters.ASSIGNED)}>
+ // {activeFilter === Filters.ASSIGNED ? (
+ //
+ // ) : (
+ //
+ // )}
+ // assigned
+ //
+ // onSetActiveFilter(Filters.COMMENT)}>
+ // {activeFilter === Filters.COMMENT ? (
+ //
+ // ) : (
+ //
+ // )}
+ // commented
+ //
+ //
+ //
+ //
+ // {/* Last week's statistics */}
+ //
+ // {lastWeekStats.map((dayStats, i) => (
+ //
+ // ))}
+ //
+ // {/* This week's ongoing statistics */}
+ //
+ // {thisWeekStats.map((dayStats, i) => (
+ // = i + 1}
+ // />
+ // ))}
+ //
+ // {/* Wrapper for tooltips */}
+ // {/*
+ // {thisWeekStats.map((dayStats, i) => (
+ //
+ // ))}
+ // */}
+ //
+ //
+ //
+ //
+ // Report bugs
+ // Submit feedback
+ // See source code
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ // onFetchNotifications()) : undefined}
+ // />
+ //
+ //
+ // {
+ // const response = window.confirm('Are you sure you want to mark all your notifications as read?');
+ // if (response) {
+ // onMarkAllAsStaged();
+ // }
+ // }) : undefined}
+ // />
+ //
+ //
+ // {
+ // const response = window.confirm('Are you sure you want to clear the cache?');
+ // if (response) {
+ // onClearCache();
+ // }
+ // }) : undefined}
+ // />
+ //
+ //
+ // {
+ // switch(notificationsPermission) {
+ // case 'granted':
+ // return setNotificationsPermission('denied');
+ // case 'denied':
+ // case 'default':
+ // default:
+ // Notification.requestPermission().then(result => {
+ // return setNotificationsPermission(result);
+ // });
+ // }
+ // }) : undefined}
+ // />
+ //
+ // {query ? (
+ //
+ //
+ //
+ // onClearQuery()) : undefined}
+ // />
+ //
+ //
+ // ) : null}
+ //
+ //
+ //
+ // onChangePage(page - 1)) : undefined}
+ // />
+ //
+ //
+ // onChangePage(page + 1)) : undefined}
+ // />
+ //
+ //
+ //
+ //
+ // onSetActiveStatus(Status.QUEUED)}
+ // href="javascript:void(0);">
+ // Unread
+ //
+ // onSetActiveStatus(Status.STAGED)}
+ // href="javascript:void(0);">
+ // Read
+ //
+ // onSetActiveStatus(Status.CLOSED)}
+ // href="javascript:void(0);">
+ // Archived
+ //
+ //
+ //
+ //
+ // {isFetchingNotifications ? (
+ //
+ //
+ //
+ // ) : fetchingNotificationsError ? (
+ //
+ //
+ // An error occurred when fetching notifications.
+ // onFetchNotifications()} href="#">Try again?
+ //
+ //
+ // ) : notifications.length <= 0 ? (
+ //
+ //
+ // No
+ // {activeStatus === Status.QUEUED ? (
+ // ' unread '
+ // ) : activeStatus === Status.STAGED ? (
+ // ' read '
+ // ) : (
+ // ' archived '
+ // )}
+ // notifications
+ //
+ //
+ // 🎉 You're all set here for the moment
+ //
+ // ) : (
+ //
+ //
+ //
+ // {notifications.map(n => (
+ //
+ //
+ //
+ // {getPRIssueIcon(n.type, n.reasons)}
+ //
+ //
+ // {
+ // window.open(n.url);
+ // onStageThread(n.id, n.repository)
+ // }}>
+ //
+ // {n.name}
+ //
+ //
+ // {getRelativeTime(n.updated_at)}
+ // {n.isAuthor && (
+ //
+ // )}
+ //
+ //
+ //
+ // {getMessageFromReasons(n.reasons, n.type)}
+ //
+ //
+ //
+ //
+ // {activeStatus === Status.QUEUED && n.badges.map(badge => {
+ // switch (badge) {
+ // case Badges.HOT:
+ // // lots of `reasons` within short time frame
+ // return (
+ //
+ // );
+ // case Badges.OLD:
+ // // old
+ // return (
+ //
+ // );
+ // case Badges.COMMENTS:
+ // // lots of `reasons`
+ // return (
+ //
+ // );
+ // default:
+ // return null;
+ // }
+ // })}
+ //
+ //
+ //
+ // window.open(n.repositoryUrl)}
+ // style={{cursor: 'pointer', userSelect: 'none'}}>
+ // {n.repository}
+ //
+ //
+ //
+ // +{n.score}
+ //
+ // {activeStatus === Status.QUEUED ? (
+ //
+ // onStageThread(n.id, n.repository)) : undefined}
+ // />
+ //
+ // ) : (
+ //
+ // onRestoreThread(n.id)) : undefined}
+ // />
+ //
+ // )}
+ // {activeStatus === Status.CLOSED ? (
+ //
+ //
+ //
+ // ) : (
+ //
+ // onMarkAsRead(n.id, n.repository)) : undefined}
+ // />
+ //
+ // )}
+ //
+ //
+ // ))}
+ //
+ //
+ // {!loading && Page {page} out of {lastPage} }
+ //
+ // )}
+ //
+ //
+ //
+ //
+ //
+ // );
}
diff --git a/src/pages/Notifications/index.js b/src/pages/Notifications/index.js
index 460de44..28b9062 100644
--- a/src/pages/Notifications/index.js
+++ b/src/pages/Notifications/index.js
@@ -148,7 +148,8 @@ class NotificationsPage extends React.Component {
activeStatus: View.UNREAD,
currentPage: 1,
sort: Sort.SCORE,
- descending: false
+ descending: false,
+ user: null
}
componentDidMount () {
@@ -160,6 +161,9 @@ class NotificationsPage extends React.Component {
}
this.props.notificationsApi.fetchNotifications();
+ this.props.notificationsApi.requestUser().then(user => {
+ this.setState({user});
+ });
this.tabSyncer = setInterval(() => {
if (!document.hidden && this.isUnreadTab) {
@@ -216,6 +220,7 @@ class NotificationsPage extends React.Component {
// Ignore empty queries.
if (text.length <= 0) {
+ this.onClearQuery();
return;
}
@@ -335,9 +340,9 @@ class NotificationsPage extends React.Component {
const filteredNotifications = notifications.filter(filterMethod);
- const notificationsQueued = filteredNotifications.filter(n => n.status === Status.QUEUED);
- const notificationsStaged = filteredNotifications.filter(n => n.status === Status.STAGED);
- const notificationsClosed = filteredNotifications.filter(n => n.status === Status.CLOSED);
+ let notificationsQueued = filteredNotifications.filter(n => n.status === Status.QUEUED);
+ let notificationsStaged = filteredNotifications.filter(n => n.status === Status.STAGED);
+ let notificationsClosed = filteredNotifications.filter(n => n.status === Status.CLOSED);
let notificationsToRender = [];
switch (this.state.activeStatus) {
@@ -389,7 +394,16 @@ class NotificationsPage extends React.Component {
if (this.state.query) {
scoredAndSortedNotifications = scoredAndSortedNotifications.filter(n => (
n.name.toLowerCase().indexOf(this.state.query.toLowerCase()) > -1)
- )
+ );
+ notificationsQueued = notificationsQueued.filter(n => (
+ n.name.toLowerCase().indexOf(this.state.query.toLowerCase()) > -1)
+ );
+ notificationsStaged = notificationsStaged.filter(n => (
+ n.name.toLowerCase().indexOf(this.state.query.toLowerCase()) > -1)
+ );
+ notificationsClosed = notificationsClosed.filter(n => (
+ n.name.toLowerCase().indexOf(this.state.query.toLowerCase()) > -1)
+ );
}
if (this.props.notificationsApi.newChanges) {
@@ -465,9 +479,9 @@ class NotificationsPage extends React.Component {
isFirstTimeUser={this.state.isFirstTimeUser}
setNotificationsPermission={this.setNotificationsPermission}
notificationsPermission={notificationsPermission}
- queuedCount={queuedCount}
- stagedCount={stagedCount}
- closedCount={closedCount}
+ unreadCount={queuedCount}
+ readCount={stagedCount}
+ archivedCount={closedCount}
stagedTodayCount={stagedTodayCount || 0}
first={firstNumbered}
last={lastNumbered}
@@ -503,6 +517,7 @@ class NotificationsPage extends React.Component {
setDescending={descending => this.setState({descending})}
view={this.state.activeStatus}
setView={this.onSetActiveStatus}
+ user={this.state.user}
/>
);
}
diff --git a/src/pages/Notifications/redesign/Scene.js b/src/pages/Notifications/redesign/Scene.js
index 8b9c101..ca6a6b4 100644
--- a/src/pages/Notifications/redesign/Scene.js
+++ b/src/pages/Notifications/redesign/Scene.js
@@ -3,10 +3,12 @@
import React from 'react';
import moment from 'moment';
import styled from '@emotion/styled';
-import {css, jsx} from '@emotion/core';
+import {css, jsx, keyframes} from '@emotion/core';
import {useSpring, useTransition, animated} from 'react-spring'
import Icon from '../../../components/Icon';
+import Logo from '../../../components/Logo';
import LoadingIcon from '../../../components/LoadingIcon';
+import {Reasons, Badges} from '../../../constants/reasons';
import {withOnEnter} from '../../../enhance';
import {Sort, View} from '../index';
@@ -19,8 +21,21 @@ const Mode = {
COMMENTED: 2
};
-// @TODO if GitHub ever fixes their API, we can use `reasons` to know when
-// a PR/Issue merges/closes/etc.
+// ========================================================================
+// START OF 'MOVE TO A UTILS FILE'
+// ========================================================================
+
+function stringOfType (type) {
+ switch (type) {
+ case 'PullRequest':
+ return 'pull request';
+ case 'Issue':
+ return 'issue';
+ default:
+ return 'task';
+ }
+}
+
function getPRIssueIcon (type, _reasons) {
const scale = 1.5;
switch (type) {
@@ -43,21 +58,73 @@ function getPRIssueIcon (type, _reasons) {
return null;
}
}
+function getRelativeTime (time) {
+ const currentTime = moment();
+ const targetTime = moment(time);
+ const diffMinutes = currentTime.diff(targetTime, 'minutes');
+ if (diffMinutes < 1)
+ return 'Just now';
+ if (diffMinutes < 5)
+ return 'Few minutes ago';
+ if (diffMinutes < 60)
+ return diffMinutes + ' minutes ago';
+ if (diffMinutes < 60 * 24)
+ return Math.floor(diffMinutes / 60) + ' hours ago';
-function colorOfScore (score, min, max) {
- const ratio = (score - min) / (max - min);
- if (ratio > .9) return '#ec1461';
- if (ratio > .8) return '#ec5314';
- if (ratio > .7) return '#ec5314';
- if (ratio > .6) return '#ec7b14';
- if (ratio > .5) return '#ec7b14';
- if (ratio > .4) return '#ec9914';
- if (ratio > .3) return '#ec9914';
- if (ratio > .2) return '#ecad14';
- if (ratio > .1) return '#ecad14';
- return '#ecc114';
+ const diffDays = currentTime.diff(targetTime, 'days');
+ if (diffDays === 1)
+ return 'Yesterday';
+ if (diffDays <= 7)
+ return 'Last ' + targetTime.format('dddd');
+ // @TODO implement longer diffs
+ return 'Over a week ago';
}
+function getMessageFromReasons (reasons, type) {
+ switch (reasons[reasons.length - 1].reason) {
+ case Reasons.ASSIGN:
+ return 'You were assigned';
+ case Reasons.AUTHOR:
+ return 'There was activity on this thread you created';
+ case Reasons.COMMENT:
+ return 'Somebody left a comment';
+ case Reasons.MENTION:
+ return 'You were mentioned';
+ case Reasons.REVIEW_REQUESTED:
+ return 'Your review was requested';
+ case Reasons.SUBSCRIBED:
+ return 'There was an update and you\'re subscribed';
+ case Reasons.OTHER:
+ default:
+ return 'Something was updated';
+ }
+}
+// ========================================================================
+// END OF 'MOVE TO A UTILS FILE'
+// ========================================================================
+
+function createColorOfScore (min, max) {
+ return function (score) {
+ const ratio = (score - min) / (max - min);
+ if (ratio > .9) return '#ec1461';
+ if (ratio > .8) return '#ec5314';
+ if (ratio > .7) return '#ec5314';
+ if (ratio > .6) return '#ec7b14';
+ if (ratio > .5) return '#ec7b14';
+ if (ratio > .4) return '#ec9914';
+ if (ratio > .3) return '#ec9914';
+ if (ratio > .2) return '#ecad14';
+ if (ratio > .1) return '#ecad14';
+ return '#ecc114';
+ }
+}
+
+const loadingKeyframe = keyframes`
+ 100% {
+ transform: translateX(100%);
+ }
+`;
+
const Container = styled('div')`
position: relative;
display: block;
@@ -219,7 +286,6 @@ const SearchField = styled('div')`
position: relative;
float: left;
text-align: left;
- // box-shadow: rgba(84,70,35,0.01) 0px 2px 19px 8px, rgba(84, 70, 35, 0.11) 0px 2px 12px;
align-items: center;
height: 36px;
font-size: 13px;
@@ -236,6 +302,7 @@ const SearchField = styled('div')`
}
&:focus-within {
border: 1px solid #457cff;
+ box-shadow: rgba(84,70,35,0.01) 0px 2px 19px 8px, rgba(84, 70, 35, 0.11) 0px 2px 12px;
}
i {
color: #bfc5d1;
@@ -271,7 +338,7 @@ const SearchInput = styled('input')`
&:focus {
opacity: 1;
color: rgb(55, 53, 47);
- width: 500px;
+ width: 300px;
}
`;
const EnhancedSearchInput = withOnEnter(SearchInput);
@@ -415,7 +482,7 @@ const NotificationRow = styled(NotificationRowHeader)`
position: relative;
background: ${WHITE};
border-radius: 4px;
- padding: 2px 18px;
+ padding: 6px 18px;
font-size: 14px;
margin-bottom: 12px;
border-radius: 6px;
@@ -424,14 +491,42 @@ const NotificationRow = styled(NotificationRowHeader)`
transition: all 200ms ease;
&:hover {
box-shadow: rgba(84,70,35,0.01) 0px 2px 19px 8px, rgba(84, 70, 35, 0.11) 0px 2px 12px;
- // background: rgb(252, 250, 248);
+ };
+ &:active {
+ background: rgb(252, 250, 248);
};
`;
-const NotificationBlock = styled('tbody')`
-
+const LoadingNotificationRow = styled(NotificationRowHeader)`
+ position: relative;
+ background: ${WHITE};
+ height: 62px;
+ overflow: hidden;
+ border-radius: 4px;
+ padding: 6px 18px;
+ font-size: 14px;
+ margin-bottom: 12px;
+ border-radius: 6px;
+ cursor: pointer;
+ user-select: none;
+ transition: all 200ms ease;
+ opacity: 0.75;
+ &:after {
+ background: linear-gradient(90deg, transparent, #f9f8f5, transparent);
+ display: block;
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ transform: translateX(-100%);
+ animation: ${loadingKeyframe} 2.0s infinite;
+ }
`;
+const NotificationBlock = styled('tbody')``;
+
const AnimatedNotificationRow = animated(NotificationRow);
const AnimatedNotificationsBlock = animated(NotificationBlock);
@@ -448,6 +543,65 @@ const NotificationCell = styled('td')`
}};
`;
+const NotificationTitle = styled('span')``;
+const NotificationByline = styled('span')`
+ display: block;
+ margin-top: 4px;
+ font-size: 12px;
+ color: #8893a7cc;
+ i {
+ margin-right: 4px;
+ font-size: 10px;
+ color: #8893a7cc;
+ }
+ span {
+ margin-left: 12px;
+ font-size: 12px;
+ font-weight: 500;
+ color: #8893a7cc;
+ i {
+ margin-right: 4px;
+ font-size: 9px;
+ color: #8893a7cc;
+ }
+ }
+`;
+
+const ProfileContainer = styled('div')`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-left: 1px solid #edeef0;
+ padding: 0 22px;
+ position: absolute;
+ right: 0;
+ transition: all 200ms ease;
+ user-select: none;
+ cursor: pointer;
+ i {
+ transition: all 200ms ease;
+ color: #bfc5d1a3
+ }
+ &:hover {
+ background: rgba(233, 233, 233, .25);
+ i {
+ color: #bfc5d1
+ }
+ }
+`;
+
+const ProfileName = styled('span')`
+ font-size: 14px;
+ font-weight: 500;
+ margin: 0 12px;
+`;
+
+const ProfilePicture = styled('img')`
+ height: 36px;
+ width: 36px;
+ border-radius: 4px;
+`;
+
const NotificationIconWrapper = styled('div')`
background: #DBE7FF;
width: 48px;
@@ -535,7 +689,7 @@ function SortingItem ({children, selected, onChange, descending, setDescending,
if (selected) {
setDescending(!descending);
} else {
- setDescending(true);
+ setDescending(false);
}
onChange(props.sort)
}}
@@ -576,7 +730,14 @@ export default function Scene ({
onClearQuery,
onSearch,
isSearching,
+ user,
+ onFetchNotifications,
+ onMarkAllAsStaged,
+ onClearCache,
+ setNotificationsPermission,
+ onStageThread,
}) {
+ console.warn('unreadCount', unreadCount)
const [menuOpen, setMenuOpen] = React.useState(false);
const [dropdownOpen, setDropdownOpen] = React.useState(false);
const [mode, setMode] = React.useState(Mode.ALL);
@@ -603,15 +764,6 @@ export default function Scene ({
// }
// });
- const props = useSpring({
- from: {opacity: 0, transform: 'translate3d(50px, 0, 0)'},
- to: {opacity: 1, transform: 'translate3d(0, 0, 0)'},
- config: {
- tension: 300,
- duration: 200,
- }
- });
-
React.useEffect(() => {
const body = window.document.querySelector('body');
const hideDropdownMenu = () => setDropdownOpen(false);
@@ -621,7 +773,14 @@ export default function Scene ({
return (
-
+
}
+ {
+ window.scrollTo(0, 0);
+ }}
+ size={36}
+ />
+ {user && (
+
+
+ {user.name}
+
+
+ )}
-
+
-
+
{
+ event.stopPropagation();
+ onFetchNotifications();
+ setDropdownOpen(false);
+ }}>
Reload notifications
Manually fetch new notifications instead of waiting for the sync
-
+
{
+ event.stopPropagation();
+ const response = window.confirm('Are you sure you want to mark all your notifications as read?');
+ void (response && onMarkAllAsStaged());
+ setDropdownOpen(false);
+ }}>
Mark all as read
Move all your unread notifications to the read tab
-
+
{
+ event.stopPropagation();
+ const response = window.confirm('Are you sure you want to clear the cache?');
+ void (response && onClearCache());
+ setDropdownOpen(false);
+ }}>
Empty cache
Clear all the notifications that are being tracked in your local storage
-
+
{
+ event.stopPropagation();
+ switch(notificationsPermission) {
+ case 'granted':
+ return setNotificationsPermission('denied');
+ case 'denied':
+ case 'default':
+ default:
+ Notification.requestPermission().then(result => {
+ return setNotificationsPermission(result);
+ });
+ }
+ setDropdownOpen(false);
+ }}>
Turn {hasNotificationsOn ? 'off' : 'on'} notifications
{hasNotificationsOn
@@ -740,6 +943,7 @@ export default function Scene ({
{'Unread'}
{unreadCount > 0 && (
0 && (
0 && (
-
- {notifications.map(item => (
-
- {/* Type */}
-
- {getPRIssueIcon(item.type, item.reasons)}
-
- {/* Title */}
-
- {item.name}
-
- {/* Repository */}
-
- {'@' + item.repository}
-
- {/* Score */}
-
- {'+' + item.score}
-
-
-
-
-
-
-
-
-
-
- ))}
-
+ {loading ? (
+
+
+
+
+
+
+
+
+
+ ) : (
+
+ )}
@@ -953,3 +1131,82 @@ export default function Scene ({
);
}
+
+function NotificationCollection ({
+ notifications,
+ colorOfScore,
+ onTitleClick,
+ page
+}) {
+ const props = useSpring({
+ from: {opacity: 0},
+ to: {opacity: 1},
+ config: {
+ duration: 200,
+ }
+ });
+
+ return (
+
+ {notifications.map(item => (
+
+ {/* Type */}
+
+ {getPRIssueIcon(item.type, item.reasons)}
+
+ {/* Title */}
+ {
+ window.open(item.url);
+ onTitleClick(item.id, item.repository);
+ }}
+ css={css`
+ font-weight: 500;
+ `}>
+
+ {item.name}
+
+
+ {getMessageFromReasons(item.reasons, item.type)}
+ {` ${getRelativeTime(item.updated_at).toLowerCase()}`}
+
+
+ {/* Repository */}
+ window.open(item.repositoryUrl)}
+ css={css`
+ font-weight: 500;
+ color: #8994A6;
+ `}>
+ {'@' + item.repository}
+
+ {/* Score */}
+
+ {'+' + item.score}
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+ );
+}
diff --git a/src/providers/Notifications.js b/src/providers/Notifications.js
index eea5bcb..3ea4e93 100644
--- a/src/providers/Notifications.js
+++ b/src/providers/Notifications.js
@@ -86,7 +86,9 @@ class NotificationsProvider extends React.Component {
}
// Only update if our notifications prop changes.
// All other props "changing" should NOT trigger a rerender.
- return this.props.notifications !== nextProps.notifications;
+ return (
+ this.props.notifications !== nextProps.notifications
+ );
}
// The web notificaitons API doesn't let users revoke notifications permission
@@ -99,6 +101,27 @@ class NotificationsProvider extends React.Component {
this.forceUpdate();
}
+ requestUser = () => {
+ const headers = {
+ 'Authorization': `token ${this.props.token}`,
+ 'Content-Type': 'application/json',
+ };
+
+ // @TODO probably add timestamp
+ const cachedUser = this.props.getUserItem('user-model');
+ if (cachedUser) {
+ return Promise.resolve(cachedUser);
+ }
+
+ return fetch(`${BASE_GITHUB_API_URL}/user`, {
+ method: 'GET',
+ headers: headers
+ }).then(processHeadersAndBodyJson)
+ .then(({json}) => {
+ this.props.setUserItem('user-model', json);
+ });
+ }
+
requestPage = (page = 1, optimizePolling = true) => {
// Fetch all notifications from a month ago, including ones that have been read.
// We can tell in the response if a notification has been read or not, so we can
@@ -395,6 +418,7 @@ class NotificationsProvider extends React.Component {
render () {
return this.props.children({
...this.state,
+ requestUser: this.requestUser,
notifications: this.props.notifications,
fetchNotifications: this.fetchNotifications,
fetchNotificationsSync: this.requestFetchNotifications,
diff --git a/src/styles/index.css b/src/styles/index.css
index 6c26c35..e9a8b05 100644
--- a/src/styles/index.css
+++ b/src/styles/index.css
@@ -18,12 +18,13 @@
::selection {
color: #fff;
- background: #24292e;
+ background: #4880ff;
}
html, body {
/* height: 100%; */
width: 100%;
+ scroll-behavior: smooth;
}
html, body, * {