mirror of
https://github.com/nickzuber/meteorite.git
synced 2024-08-17 07:20:40 +03:00
Deprecate /notifications-redesign to /notifications
This commit is contained in:
parent
015758fc9e
commit
9380afbeb9
10
src/App.js
10
src/App.js
@ -1,6 +1,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import amplitude from 'amplitude-js';
|
import amplitude from 'amplitude-js';
|
||||||
import {
|
import {
|
||||||
|
Redirect,
|
||||||
Router,
|
Router,
|
||||||
Location,
|
Location,
|
||||||
LocationProvider
|
LocationProvider
|
||||||
@ -12,7 +13,6 @@ import {
|
|||||||
Login,
|
Login,
|
||||||
Pricing,
|
Pricing,
|
||||||
Guide,
|
Guide,
|
||||||
Notifications,
|
|
||||||
NotificationsRedesign,
|
NotificationsRedesign,
|
||||||
} from './pages';
|
} from './pages';
|
||||||
|
|
||||||
@ -41,6 +41,10 @@ function PageTracker ({location}) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RedirectShell () {
|
||||||
|
return <Redirect noThrow to={routes.NOTIFICATIONS} />;
|
||||||
|
}
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
@ -54,8 +58,8 @@ class App extends Component {
|
|||||||
<Login path={routes.LOGIN} />
|
<Login path={routes.LOGIN} />
|
||||||
<Pricing path={routes.PRICING} />
|
<Pricing path={routes.PRICING} />
|
||||||
<Guide path={routes.GUIDE} />
|
<Guide path={routes.GUIDE} />
|
||||||
<Notifications path={routes.NOTIFICATIONS} />
|
<RedirectShell path={routes.REDESIGN_NOTIFICATIONS} />
|
||||||
<NotificationsRedesign path={routes.REDESIGN_NOTIFICATIONS} />
|
<NotificationsRedesign path={routes.NOTIFICATIONS} />
|
||||||
</Router>
|
</Router>
|
||||||
</LocationProvider>
|
</LocationProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,552 +0,0 @@
|
|||||||
/** @jsx jsx */
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import {css, jsx} from '@emotion/core';
|
|
||||||
import {Link as RouterLink} from '@reach/router';
|
|
||||||
import {routes} from '../../constants';
|
|
||||||
import {
|
|
||||||
BasicPageWrapper,
|
|
||||||
forSmallScreens,
|
|
||||||
forMobile
|
|
||||||
} from '../common';
|
|
||||||
import WorkflowToggle from './WorkflowToggle';
|
|
||||||
|
|
||||||
import {ReactComponent as CloudOffSvg} from '../../images/svg/icons/cloud_off.svg'
|
|
||||||
import {ReactComponent as NotificationsActiveSvg} from '../../images/svg/icons/notifications_active.svg'
|
|
||||||
import {ReactComponent as PriorityHighSvg} from '../../images/svg/icons/priority_high.svg'
|
|
||||||
import {ReactComponent as TuneSvg} from '../../images/svg/icons/tune.svg'
|
|
||||||
import {ReactComponent as SpeedSvg} from '../../images/svg/icons/speed.svg'
|
|
||||||
import {ReactComponent as GpsFixedSvg} from '../../images/svg/icons/gps_fixed.svg'
|
|
||||||
import {ReactComponent as WbIridescentSvg} from '../../images/svg/icons/wb_iridescent.svg'
|
|
||||||
import {ReactComponent as TimelineSvg} from '../../images/svg/icons/timeline.svg'
|
|
||||||
|
|
||||||
import ItemPng from '../../images/screenshots/item.png';
|
|
||||||
import ItemTwoPng from '../../images/screenshots/item-2.png';
|
|
||||||
import ScreenshotPng from '../../images/screenshots/new/dashboard.png';
|
|
||||||
import ScoresPng from '../../images/screenshots/new/scores.png';
|
|
||||||
import ReasonsPng from '../../images/screenshots/new/reasons.png';
|
|
||||||
|
|
||||||
import RobinLogo from '../../images/logos/robin-logo.png';
|
|
||||||
import ForwardLogo from '../../images/logos/forward-logo.png';
|
|
||||||
import FacebookLogo from '../../images/logos/facebook-logo.png';
|
|
||||||
|
|
||||||
import '../../styles/gradient.css';
|
|
||||||
import '../../styles/font.css';
|
|
||||||
|
|
||||||
const themeColor = '#27B768';
|
|
||||||
const ALT_BACKGROUND_COLOR = '#f6f2ed';
|
|
||||||
|
|
||||||
const ProductHuntButton = () => (
|
|
||||||
<a href="https://www.producthunt.com/posts/meteorite?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-meteorite" target="_blank">
|
|
||||||
<img
|
|
||||||
src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=145651&theme=dark"
|
|
||||||
alt="Meteorite - Smarter GitHub notifications. | Product Hunt Embed"
|
|
||||||
style={{width: 200, height: 43}}
|
|
||||||
width="200px"
|
|
||||||
height="43px" />
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Outer = styled('div')`
|
|
||||||
background: ${p => p.alt ? ALT_BACKGROUND_COLOR : 'none'};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Container = styled('div')`
|
|
||||||
position: relative;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: ${p => p.column ? 'column' : 'row'};
|
|
||||||
max-width: 1080px;
|
|
||||||
min-height: 100px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 1rem 0;
|
|
||||||
margin-bottom: 5rem;
|
|
||||||
${forSmallScreens(`
|
|
||||||
padding-left: 2.5rem;
|
|
||||||
padding-right: 2.5rem;
|
|
||||||
`)}
|
|
||||||
${forMobile(`
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
`)}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FlexItem = styled('div')`
|
|
||||||
flex: ${(({flex = 1}) => flex)};
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Button = styled(RouterLink)`
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 400;
|
|
||||||
color: #333333;
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: middle;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
background-color: transparent;
|
|
||||||
border: 0px solid transparent;
|
|
||||||
margin: 0rem 0.25rem;
|
|
||||||
width: max-content;
|
|
||||||
padding: 0.125rem 1rem;
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 1.75;
|
|
||||||
border-radius: 5px;
|
|
||||||
-webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
|
||||||
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
border-color: #f4f4f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: #eee;
|
|
||||||
border-color: #eee;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const MainButton = styled(Button)`
|
|
||||||
background-color: ${themeColor};
|
|
||||||
border-color: ${themeColor};
|
|
||||||
color: #fff;
|
|
||||||
font-size: 18px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #249959;
|
|
||||||
border-color: #249959;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: #20894f;
|
|
||||||
border-color: #20894f;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const HeroButton = styled(MainButton)`
|
|
||||||
font-size: 22px;
|
|
||||||
margin: -4px 8px 0;
|
|
||||||
margin-left: 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FlexBreak = styled('div')`
|
|
||||||
flex-basis: 100%;
|
|
||||||
height: 0;
|
|
||||||
min-height: ${p => p.height || 0}px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const HeroTitle = styled('h1')`
|
|
||||||
font-size: 68px;
|
|
||||||
line-height: 68px;
|
|
||||||
margin: 0 auto 6px;
|
|
||||||
font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
${forMobile(`
|
|
||||||
font-size: 52px;
|
|
||||||
line-height: 58px;
|
|
||||||
`)}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const HeroSubtitle = styled('h1')`
|
|
||||||
color: #6c757d;
|
|
||||||
font-size: 24px;
|
|
||||||
line-height: 28px;
|
|
||||||
margin: 0 auto 16px;
|
|
||||||
width: 80%;
|
|
||||||
margin-left: 0;
|
|
||||||
font-family: medium-content-sans-serif-font, Inter UI, system-ui, sans-serif;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
${forMobile(`
|
|
||||||
margin: 0 auto 32px;
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 24px;
|
|
||||||
`)}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Title = styled('h1')`
|
|
||||||
font-size: 38px;
|
|
||||||
line-height: 38px;
|
|
||||||
margin: 0 auto 12px;
|
|
||||||
font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif;
|
|
||||||
font-weight: 500;
|
|
||||||
text-align: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Subtitle = styled('h1')`
|
|
||||||
color: #6c757d;
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 24px;
|
|
||||||
margin: 0 auto 16px;
|
|
||||||
font-family: medium-content-sans-serif-font, Inter UI, system-ui, sans-serif;
|
|
||||||
font-weight: 500;
|
|
||||||
max-width: 540px;
|
|
||||||
text-align: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ItemImage = styled('img')`
|
|
||||||
width: 847px;
|
|
||||||
box-shadow: rgba(84, 70, 35, 0) 0px 4px 18px, rgba(84, 70, 35, 0.15) 0px 2px 8px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-left: ${props => props.left || 0}px;
|
|
||||||
margin-top: ${props => props.top || 0}px;
|
|
||||||
transform: rotate(${props => props.deg || 0}deg) scale(${props => props.scale || 1});
|
|
||||||
`;
|
|
||||||
|
|
||||||
const HeroLeft = styled(FlexItem)`
|
|
||||||
z-index: 1;
|
|
||||||
flex-grow: 1;
|
|
||||||
width: 50%;
|
|
||||||
${forMobile(`
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
`)}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const HeroRight = styled(FlexItem)`
|
|
||||||
flex-grow: 1;
|
|
||||||
width: 50%;
|
|
||||||
${forMobile(`
|
|
||||||
display: none;
|
|
||||||
`)}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const DotsBackground = styled('div')`
|
|
||||||
position: absolute;
|
|
||||||
height: 400px;
|
|
||||||
width: 100%;
|
|
||||||
margin-left: -60px;
|
|
||||||
background: radial-gradient(transparent 50%, #fffefd), \
|
|
||||||
url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAKElEQVQoU2NkIBIwEqmOgQ4KX715/x/mHDERQbiNGFZTXyGuUKC+rwHAcQwLu0IifQAAAABJRU5ErkJggg==) repeat;
|
|
||||||
${forMobile(`
|
|
||||||
width: 90%;
|
|
||||||
margin: 0 auto;
|
|
||||||
`)}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FooterImageContainer = styled('div')`
|
|
||||||
position: relative;
|
|
||||||
height: 250px;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
background: radial-gradient(transparent 50%, ${ALT_BACKGROUND_COLOR}), \
|
|
||||||
url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAKElEQVQoU2NkIBIwEqmOgQ4KX715/x/mHDERQbiNGFZTXyGuUKC+rwHAcQwLu0IifQAAAABJRU5ErkJggg==) repeat;
|
|
||||||
${forMobile(`
|
|
||||||
display: none;
|
|
||||||
`)}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CompanyQuotesContainer = styled('div')`
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 4rem;
|
|
||||||
${forMobile(`
|
|
||||||
flex-direction: column;
|
|
||||||
max-width: 300px;
|
|
||||||
margin: 0 auto;
|
|
||||||
`)}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const HorizontalFlexContainer = styled('div')`
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 4rem;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FeatureItem = styled('div')(({color}) => `
|
|
||||||
flex: 1;
|
|
||||||
${forMobile(`
|
|
||||||
flex: 1 0 34%;
|
|
||||||
`)}
|
|
||||||
flex-direction: column;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 4rem;
|
|
||||||
padding: 0 12px;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
background: ${color}28;
|
|
||||||
fill: ${color};
|
|
||||||
border-radius: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 24px;
|
|
||||||
line-height: 25px;
|
|
||||||
text-align: center;
|
|
||||||
margin: 18px auto 8px;
|
|
||||||
font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 17px;
|
|
||||||
line-height: 22px;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const HorizontalListItem = styled('div')(({last}) => `
|
|
||||||
flex: 1;
|
|
||||||
border-right: ${last ? '0px' : '1px'} solid rgba(214, 212, 209, 0.3);
|
|
||||||
padding: 0 32px;
|
|
||||||
${forMobile(`
|
|
||||||
border-right: 0;
|
|
||||||
border-bottom: ${last ? '0px' : '1px'} solid rgba(214, 212, 209, 0.3);
|
|
||||||
padding: 32px 0;
|
|
||||||
`)}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const Quote = styled('p')`
|
|
||||||
margin: 0;
|
|
||||||
font-family: medium-content-title-font, Inter UI, sans-serif;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 400;
|
|
||||||
&:before {
|
|
||||||
content: open-quote;
|
|
||||||
}
|
|
||||||
&:after {
|
|
||||||
content: close-quote;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CompanyPerson = styled('div')`
|
|
||||||
transform: scale(0.95);
|
|
||||||
display: flex;
|
|
||||||
padding: 8px 0;
|
|
||||||
margin: 0;
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
height: 30px;
|
|
||||||
width: 30px;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
span {
|
|
||||||
display: block;
|
|
||||||
padding: 0 16px;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 20px;
|
|
||||||
color: #37352f80;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default function Scene (props) {
|
|
||||||
const baseItemOffset = {
|
|
||||||
scale: 0.1,
|
|
||||||
top: -185,
|
|
||||||
left: 20
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BasicPageWrapper {...props}>
|
|
||||||
|
|
||||||
{/* Hero */}
|
|
||||||
<Container css={css`
|
|
||||||
${forMobile(`
|
|
||||||
flex-direction: column;
|
|
||||||
`)}
|
|
||||||
`}>
|
|
||||||
<HeroLeft>
|
|
||||||
<HeroTitle>{'Manage your notifications.'}</HeroTitle>
|
|
||||||
<HeroSubtitle>{'Meteorite helps organize, filter, and prioritize your\
|
|
||||||
GitHub notifications to make your life easier'}</HeroSubtitle>
|
|
||||||
<FlexBreak />
|
|
||||||
<div css={css`
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
${forMobile(`
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 300px;
|
|
||||||
`)}
|
|
||||||
`}>
|
|
||||||
<HeroButton css={css`${forMobile(`margin-bottom: 12px;`)}`} to={routes.LOGIN}>{'Login / Sign up'}</HeroButton>
|
|
||||||
<ProductHuntButton />
|
|
||||||
</div>
|
|
||||||
</HeroLeft>
|
|
||||||
<HeroRight>
|
|
||||||
<DotsBackground />
|
|
||||||
<ItemImage
|
|
||||||
scale={baseItemOffset.scale + 0.8}
|
|
||||||
left={baseItemOffset.left + 8}
|
|
||||||
top={25}
|
|
||||||
src={ItemTwoPng} />
|
|
||||||
<ItemImage
|
|
||||||
deg={0}
|
|
||||||
scale={baseItemOffset.scale + 0.95}
|
|
||||||
left={baseItemOffset.left}
|
|
||||||
top={baseItemOffset.top + 4}
|
|
||||||
src={ItemPng} />
|
|
||||||
</HeroRight>
|
|
||||||
</Container>
|
|
||||||
|
|
||||||
{/* Testimonials */}
|
|
||||||
<Container column>
|
|
||||||
<FlexBreak height={60} />
|
|
||||||
<Title>{'Hear what others are saying'}</Title>
|
|
||||||
<Subtitle>{'Loved by other human beings, just like you'}</Subtitle>
|
|
||||||
<CompanyQuotesContainer>
|
|
||||||
<HorizontalListItem>
|
|
||||||
<Quote>{`So good! I love the importance sorting!`}</Quote>
|
|
||||||
<CompanyPerson>
|
|
||||||
<img src={FacebookLogo} />
|
|
||||||
<span>
|
|
||||||
{'— Mike Grabowski'}<br />
|
|
||||||
{'Software Architect, React Native'}
|
|
||||||
</span>
|
|
||||||
</CompanyPerson>
|
|
||||||
</HorizontalListItem>
|
|
||||||
<HorizontalListItem>
|
|
||||||
<Quote>{`I've been using it for a bit and it's so useful, especially if you use GitHub for work.`}</Quote>
|
|
||||||
<CompanyPerson>
|
|
||||||
<img src={RobinLogo} />
|
|
||||||
<span>
|
|
||||||
{'— Trevor Suarez'}<br />
|
|
||||||
{'Robin, Backend Software Engineer'}
|
|
||||||
</span>
|
|
||||||
</CompanyPerson>
|
|
||||||
</HorizontalListItem>
|
|
||||||
<HorizontalListItem last>
|
|
||||||
<Quote>{`Awww sh*t, nice.`}</Quote>
|
|
||||||
<CompanyPerson>
|
|
||||||
<img src={ForwardLogo} />
|
|
||||||
<span>
|
|
||||||
{'— Chris Walker'}<br />
|
|
||||||
{'Forward, Software Engineer'}
|
|
||||||
</span>
|
|
||||||
</CompanyPerson>
|
|
||||||
</HorizontalListItem>
|
|
||||||
</CompanyQuotesContainer>
|
|
||||||
</Container>
|
|
||||||
|
|
||||||
{/* Lifecycle */}
|
|
||||||
<Outer alt>
|
|
||||||
<Container column>
|
|
||||||
<FlexBreak height={60} />
|
|
||||||
<Title>{'The Notification Lifecycle'}</Title>
|
|
||||||
<Subtitle>{'GitHub notifications can actually be your friend – with a little bit of discipline.\
|
|
||||||
All we need to do is filter out the noise & organize things in a way that makes sense.'}</Subtitle>
|
|
||||||
<WorkflowToggle
|
|
||||||
easeTimingMs={100}
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
id: 0,
|
|
||||||
title: 'Filter The Noise',
|
|
||||||
description: 'Stay focused on the important things. We\'ll only show the notifications that matter to you.',
|
|
||||||
image: ScreenshotPng
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: 'Highlight The Callouts',
|
|
||||||
description: 'When things stand out, you shouldn\'t miss it. We mark notifications when there\'s something interesting going down.',
|
|
||||||
image: ReasonsPng
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: 'Sort By Importance',
|
|
||||||
description: 'Don\'t get lost at sea – the most important notifications stay at the top of the list.',
|
|
||||||
image: ScoresPng
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<FlexBreak height={100} />
|
|
||||||
<Title css={css`font-size: 32px; line-height: 38px; margin-bottom: 24px;`}>{'Start using Meteorite absolutely free'}</Title>
|
|
||||||
<HeroButton to={routes.LOGIN} css={css`margin: 0 auto;`}>{'Login / Sign up'}</HeroButton>
|
|
||||||
<FlexBreak height={80} />
|
|
||||||
</Container>
|
|
||||||
</Outer>
|
|
||||||
|
|
||||||
{/* Features */}
|
|
||||||
<Container column>
|
|
||||||
<FlexBreak height={20} />
|
|
||||||
<Title>{'Features built for getting things done.'}</Title>
|
|
||||||
<Subtitle>{'All of the features of Meteorite are specifically designed'}<br />
|
|
||||||
{'for optimizing your workday'}</Subtitle>
|
|
||||||
<HorizontalFlexContainer>
|
|
||||||
<FeatureItem color={'#9C27B0'}>
|
|
||||||
<CloudOffSvg />
|
|
||||||
<h3>{'Serverless'}</h3>
|
|
||||||
<p>{'Any notification scoring and storing is done completely offline.'}</p>
|
|
||||||
</FeatureItem>
|
|
||||||
<FeatureItem color={'#ffc915'}>
|
|
||||||
<NotificationsActiveSvg />
|
|
||||||
<h3>{'Desktop Notifications'}</h3>
|
|
||||||
<p>{'Get notified when we do – ability to turn on desktop notifications.'}</p>
|
|
||||||
</FeatureItem>
|
|
||||||
<FeatureItem color={'#27B768'}>
|
|
||||||
<PriorityHighSvg />
|
|
||||||
<h3>{'Auto Sorting'}</h3>
|
|
||||||
<p>{'Keep your most important notifications at the top of the list.'}</p>
|
|
||||||
</FeatureItem>
|
|
||||||
<FeatureItem color={'#00A0F5'}>
|
|
||||||
<TuneSvg />
|
|
||||||
<h3>{'Filter Noise'}</h3>
|
|
||||||
<p>{'Any notifications that don\'t directly involve you are hidden.'}</p>
|
|
||||||
</FeatureItem>
|
|
||||||
<FlexBreak />
|
|
||||||
<FeatureItem color={'#EE3F46'}>
|
|
||||||
<SpeedSvg />
|
|
||||||
<h3>{'Dead Simple'}</h3>
|
|
||||||
<p>{'No integrations – just log in and start working.'}</p>
|
|
||||||
</FeatureItem>
|
|
||||||
<FeatureItem color={'#10293c'}>
|
|
||||||
<GpsFixedSvg />
|
|
||||||
<h3>{'Live Updates'}</h3>
|
|
||||||
<p>{'All of your notifications are processed in real time.'}</p>
|
|
||||||
</FeatureItem>
|
|
||||||
<FeatureItem color={'#fd9446'}>
|
|
||||||
<WbIridescentSvg />
|
|
||||||
<h3>{'Reasoning'}</h3>
|
|
||||||
<p>{'We\'ll also tell you why you\'re getting each notification.'}</p>
|
|
||||||
</FeatureItem>
|
|
||||||
<FeatureItem color={'#fc46fd'}>
|
|
||||||
<TimelineSvg />
|
|
||||||
<h3>{'Statistics'}</h3>
|
|
||||||
<p>{'Better understand how you work with data visualizations.'}</p>
|
|
||||||
</FeatureItem>
|
|
||||||
</HorizontalFlexContainer>
|
|
||||||
</Container>
|
|
||||||
|
|
||||||
{/* Closer */}
|
|
||||||
<Outer alt>
|
|
||||||
<Container column>
|
|
||||||
<FlexBreak height={80} />
|
|
||||||
<Title>{'Better notifications for everyone.'}</Title>
|
|
||||||
<FlexBreak height={20} />
|
|
||||||
<HeroButton to={routes.LOGIN} css={css`margin: 0 auto;`}>{'Login / Sign up'}</HeroButton>
|
|
||||||
<FlexBreak height={40} />
|
|
||||||
<FooterImageContainer>
|
|
||||||
<ItemImage
|
|
||||||
scale={baseItemOffset.scale + 0.8}
|
|
||||||
top={60}
|
|
||||||
src={ItemPng} />
|
|
||||||
<ItemImage
|
|
||||||
deg={0}
|
|
||||||
scale={baseItemOffset.scale + 0.95}
|
|
||||||
top={-30}
|
|
||||||
src={ItemTwoPng} />
|
|
||||||
</FooterImageContainer>
|
|
||||||
<FlexBreak height={40} />
|
|
||||||
</Container>
|
|
||||||
</Outer>
|
|
||||||
</BasicPageWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
@ -3,7 +3,7 @@ import { compose } from 'recompose';
|
|||||||
import { withAuthProvider } from '../../providers/Auth';
|
import { withAuthProvider } from '../../providers/Auth';
|
||||||
import { withCookiesProvider } from '../../providers/Cookies';
|
import { withCookiesProvider } from '../../providers/Cookies';
|
||||||
import { OAUTH_TOKEN_COOKIE } from '../../constants/cookies';
|
import { OAUTH_TOKEN_COOKIE } from '../../constants/cookies';
|
||||||
import Scene from './Scene.new';
|
import Scene from './Scene';
|
||||||
|
|
||||||
class HomePage extends React.Component {
|
class HomePage extends React.Component {
|
||||||
onLogout = () => {
|
onLogout = () => {
|
||||||
|
@ -24,7 +24,7 @@ class LoginPage extends React.Component {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
if (this.props.authApi.token) {
|
if (this.props.authApi.token) {
|
||||||
return <Redirect noThrow to={routes.REDESIGN_NOTIFICATIONS} />
|
return <Redirect noThrow to={routes.NOTIFICATIONS} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,466 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import moment from 'moment';
|
|
||||||
import { Redirect, navigate } from "@reach/router";
|
|
||||||
import { compose } from 'recompose';
|
|
||||||
import { withNotificationsProvider } from '../../providers/Notifications';
|
|
||||||
import { withAuthProvider } from '../../providers/Auth';
|
|
||||||
import { withCookiesProvider } from '../../providers/Cookies';
|
|
||||||
import { withStorageProvider } from '../../providers/Storage';
|
|
||||||
import { OAUTH_TOKEN_COOKIE } from '../../constants/cookies';
|
|
||||||
import { routes } from '../../constants';
|
|
||||||
import { Filters } from '../../constants/filters';
|
|
||||||
import { Status } from '../../constants/status';
|
|
||||||
import { Reasons, Badges } from '../../constants/reasons';
|
|
||||||
import Scene, { getMessageFromReasons } from './Scene';
|
|
||||||
import issueIcon from '../../images/issue-bg.png';
|
|
||||||
import prIcon from '../../images/pr-bg.png';
|
|
||||||
import tabIcon from '../../images/icon.png';
|
|
||||||
import tabDotIcon from '../../images/iconDot.png';
|
|
||||||
|
|
||||||
const PER_PAGE = 10;
|
|
||||||
|
|
||||||
// @TODO Move these functions.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a notification, give it a score based on its importance.
|
|
||||||
*
|
|
||||||
* There are some interesting workarounds that go into this algorithm to account
|
|
||||||
* for GitHub's broken notifications API -- but we will get to that later. First,
|
|
||||||
* let's start off with the basics of scoring.
|
|
||||||
*
|
|
||||||
* There are a few "reasons" that we can be getting a notification, each having
|
|
||||||
* an initial weight of importance:
|
|
||||||
*
|
|
||||||
* - MENTION -> 8
|
|
||||||
* - ASSIGN -> 14
|
|
||||||
* - REVIEW_REQUESTED -> 30
|
|
||||||
* - SUBSCRIBED -> 3
|
|
||||||
* - COMMENT -> 3
|
|
||||||
* - AUTHOR -> 10
|
|
||||||
* - OTHER -> 2
|
|
||||||
*
|
|
||||||
* There are some rules that go to giving out these scores, primarily being the
|
|
||||||
* first time we see one of these unique reasons, we award the notification with
|
|
||||||
* the respective score, but a reason that transitions into itself will be awarded
|
|
||||||
* a degraded score of min(ceil(n/3), 2). For example:
|
|
||||||
*
|
|
||||||
* - null, MENTION, MENTION -> 0, 8, 3
|
|
||||||
* - null, ASSIGN, ASSIGN, REVIEW_REQUESTED, -> 0, 14, 5, 20
|
|
||||||
* - null, SUBSCRIBED, SUBSCRIBED, SUBSCRIBED -> 0, 3, 2, 2
|
|
||||||
*
|
|
||||||
* @param {Object} notification Some notification to score.
|
|
||||||
* @return {number} The score.
|
|
||||||
*/
|
|
||||||
function scoreOf (notification) {
|
|
||||||
const {reasons} = notification;
|
|
||||||
let score = 0;
|
|
||||||
let prevReason = null;
|
|
||||||
for (let i = 0; i < reasons.length; i++) {
|
|
||||||
const reason = reasons[i].reason;
|
|
||||||
if (prevReason && reason === prevReason) {
|
|
||||||
const degradedScore = Math.ceil(scoreOfReason[reason] / 3);
|
|
||||||
score += Math.max(degradedScore, 2);
|
|
||||||
} else {
|
|
||||||
score += scoreOfReason[reason];
|
|
||||||
}
|
|
||||||
prevReason = reason;
|
|
||||||
}
|
|
||||||
return score;
|
|
||||||
};
|
|
||||||
|
|
||||||
function badgesOf (notification) {
|
|
||||||
const badges = [];
|
|
||||||
const len = notification.reasons.length;
|
|
||||||
const timeSinceLastUpdate = moment().diff(moment(notification.reasons[len - 1].time), 'minutes');
|
|
||||||
|
|
||||||
// If there are more than 4 reasons, and the last 4 reasons have happened within
|
|
||||||
// an hour of each other. The last update should be within the past 30 minutes.
|
|
||||||
// The specific time frame and reasons count is subject to change.
|
|
||||||
if (len >= 4 && timeSinceLastUpdate < 30) {
|
|
||||||
const oldestReference = moment(notification.reasons[len - 4].time);
|
|
||||||
const newestReference = moment(notification.reasons[len - 1].time);
|
|
||||||
if (newestReference.diff(oldestReference, 'hours') <= 1) {
|
|
||||||
badges.push(Badges.HOT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If there's a lot of activity going on within the thread in general.
|
|
||||||
// The specific nunmber should be relative to average number of thread lengths.
|
|
||||||
// We can track a running statistic as we see notifications update.
|
|
||||||
if (len > 6) {
|
|
||||||
badges.push(Badges.COMMENTS);
|
|
||||||
}
|
|
||||||
// If you've been tagged in for review and the most recent update happened over
|
|
||||||
// 4 hours ago, that specific time is subject to change.
|
|
||||||
if (notification.reasons.some(r => r.reason === Reasons.REVIEW_REQUESTED) &&
|
|
||||||
timeSinceLastUpdate > 60 * 4) {
|
|
||||||
badges.push(Badges.OLD);
|
|
||||||
}
|
|
||||||
return badges;
|
|
||||||
};
|
|
||||||
|
|
||||||
const scoreOfReason = {
|
|
||||||
[Reasons.ASSIGN]: 21,
|
|
||||||
[Reasons.AUTHOR]: 11,
|
|
||||||
[Reasons.MENTION]: 17,
|
|
||||||
[Reasons.TEAM_MENTION]: 11,
|
|
||||||
[Reasons.OTHER]: 4,
|
|
||||||
[Reasons.REVIEW_REQUESTED]: 29,
|
|
||||||
[Reasons.SUBSCRIBED]: 3,
|
|
||||||
[Reasons.COMMENT]: 6,
|
|
||||||
[Reasons.STATE_CHANGE]: 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
const decorateWithScore = notification => ({
|
|
||||||
...notification,
|
|
||||||
score: scoreOf(notification),
|
|
||||||
badges: badgesOf(notification)
|
|
||||||
});
|
|
||||||
|
|
||||||
class NotificationsPage extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.notificationSent = false;
|
|
||||||
this.isUnreadTab = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
|
||||||
currentTime: moment(),
|
|
||||||
error: null,
|
|
||||||
notificationSent: false,
|
|
||||||
isFirstTimeUser: false,
|
|
||||||
isSearching: false,
|
|
||||||
query: null,
|
|
||||||
activeFilter: Filters.PARTICIPATING,
|
|
||||||
activeStatus: Status.QUEUED,
|
|
||||||
currentPage: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
const isFirstTimeUser = !this.props.storageApi.getUserItem('hasOnboarded');
|
|
||||||
|
|
||||||
// Harsh but fair.
|
|
||||||
if (window.outerWidth < 1100) {
|
|
||||||
navigate(routes.REDESIGN_NOTIFICATIONS);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFirstTimeUser) {
|
|
||||||
this.setState({isFirstTimeUser: true});
|
|
||||||
// this.props.storageApi.setUserItem('hasOnboarded', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.notificationsApi.fetchNotifications();
|
|
||||||
|
|
||||||
this.tabSyncer = setInterval(() => {
|
|
||||||
if (!document.hidden && this.isUnreadTab) {
|
|
||||||
this.updateTabIcon(false);
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
this.syncer = setInterval(() => {
|
|
||||||
this.props.notificationsApi.fetchNotificationsSync()
|
|
||||||
.then(error => this.setState({error: null}))
|
|
||||||
.catch(error => this.setState({error}));
|
|
||||||
this.setState({currentTime: moment()});
|
|
||||||
}, 8 * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate (nextProps, nextState) {
|
|
||||||
if (this.props.notificationsApi.newChanges !== nextProps.notificationsApi.newChanges) {
|
|
||||||
this.notificationSent = false;
|
|
||||||
}
|
|
||||||
// The idea here is if we've just updated the prevNotifications state, then
|
|
||||||
// we don't want to trigger a rerender.
|
|
||||||
return nextState.prevNotifications === this.state.prevNotifications;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
clearInterval(this.syncer);
|
|
||||||
clearInterval(this.tabSyncer);
|
|
||||||
}
|
|
||||||
|
|
||||||
onChangePage = page => {
|
|
||||||
this.setState({ currentPage: page });
|
|
||||||
}
|
|
||||||
|
|
||||||
onSetActiveFilter = filter => {
|
|
||||||
this.setState({ activeFilter: filter, currentPage: 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
onSetActiveStatus = status => {
|
|
||||||
this.setState({ activeStatus: status, currentPage: 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
onClearQuery = () => {
|
|
||||||
this.setState({ query: null });
|
|
||||||
}
|
|
||||||
|
|
||||||
onLogout = () => {
|
|
||||||
// Remove cookie and invalidate token on client.
|
|
||||||
this.props.cookiesApi.removeCookie(OAUTH_TOKEN_COOKIE);
|
|
||||||
this.props.authApi.invalidateToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
onSearch = event => {
|
|
||||||
const text = event.target.value;
|
|
||||||
|
|
||||||
// Ignore empty queries.
|
|
||||||
if (text.length <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ isSearching: true });
|
|
||||||
setTimeout(() => {
|
|
||||||
this.setState({
|
|
||||||
query: text,
|
|
||||||
isSearching: false
|
|
||||||
});
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
enhancedOnStageThread = (thread_id, repository) => {
|
|
||||||
this.props.storageApi.incrStat('stagedCount');
|
|
||||||
this.props.storageApi.incrStat(repository + '-stagedCount');
|
|
||||||
this.props.notificationsApi.stageThread(thread_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
enhancedOnMarkAsRead = (thread_id, repository) => {
|
|
||||||
this.props.storageApi.incrStat('stagedCount');
|
|
||||||
this.props.storageApi.incrStat(repository + '-stagedCount');
|
|
||||||
this.props.notificationsApi.markAsRead(thread_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreThread = thread_id => {
|
|
||||||
this.props.notificationsApi.restoreThread(thread_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
setNotificationsPermission = (...args) => {
|
|
||||||
this.props.notificationsApi.setNotificationsPermission(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTabIcon (hasUnread = true) {
|
|
||||||
this.isUnreadTab = hasUnread;
|
|
||||||
var link = document.querySelector("link[rel*='icon']") || document.createElement('link');
|
|
||||||
link.rel = 'shortcut icon';
|
|
||||||
link.href = hasUnread ? tabDotIcon : tabIcon;
|
|
||||||
document.getElementsByTagName('head')[0].appendChild(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendWebNotification = newNotifcations => {
|
|
||||||
if (this.notificationSent || newNotifcations.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only show these notifications and title change if the tab is out of focus.
|
|
||||||
if (!document.hidden) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set this even if we don't actually send the notification due to permissions.
|
|
||||||
this.notificationSent = true;
|
|
||||||
this.updateTabIcon();
|
|
||||||
|
|
||||||
// No permission, no notification.
|
|
||||||
if (this.props.notificationsApi.notificationsPermission !== 'granted') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const n = newNotifcations[0];
|
|
||||||
const reasonByline = getMessageFromReasons(n.reasons, n.type);
|
|
||||||
|
|
||||||
const additionalInfo = newNotifcations.length > 1
|
|
||||||
? ` (+${newNotifcations.length} more)`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const notification = new Notification(n.name + additionalInfo, {
|
|
||||||
body: reasonByline,
|
|
||||||
icon: n.type === "Issue" ? issueIcon : prIcon,
|
|
||||||
badge: n.type === "Issue" ? issueIcon : prIcon,
|
|
||||||
requireInteraction: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
notification.addEventListener('click', () => {
|
|
||||||
this.updateTabIcon(false);
|
|
||||||
this.enhancedOnStageThread(n.id, n.repository);
|
|
||||||
window.open(n.url);
|
|
||||||
})
|
|
||||||
|
|
||||||
// Manually close for legacy browser support.
|
|
||||||
setTimeout(notification.close.bind(notification), 10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
getFilteredNotifications = () => {
|
|
||||||
const {notifications} = this.props.notificationsApi;
|
|
||||||
|
|
||||||
let filterMethod = () => true;
|
|
||||||
switch (this.state.activeFilter) {
|
|
||||||
case Filters.PARTICIPATING:
|
|
||||||
filterMethod = n => (
|
|
||||||
n.reasons.some(({ reason }) => (
|
|
||||||
reason === Reasons.REVIEW_REQUESTED ||
|
|
||||||
reason === Reasons.ASSIGN ||
|
|
||||||
reason === Reasons.MENTION ||
|
|
||||||
reason === Reasons.AUTHOR
|
|
||||||
))
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Filters.ASSIGNED:
|
|
||||||
filterMethod = n => (
|
|
||||||
n.reasons.some(({ reason }) => reason === Reasons.ASSIGN)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Filters.REVIEW_REQUESTED:
|
|
||||||
filterMethod = n => (
|
|
||||||
n.reasons.some(({ reason }) => reason === Reasons.REVIEW_REQUESTED)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Filters.COMMENT:
|
|
||||||
filterMethod = n => (
|
|
||||||
n.reasons.some(({ reason }) => reason === Reasons.COMMENT)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
filterMethod = () => true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 notificationsToRender = [];
|
|
||||||
switch (this.state.activeStatus) {
|
|
||||||
case Status.CLOSED:
|
|
||||||
notificationsToRender = notificationsClosed;
|
|
||||||
break;
|
|
||||||
case Status.STAGED:
|
|
||||||
notificationsToRender = notificationsStaged;
|
|
||||||
break;
|
|
||||||
case Status.QUEUED:
|
|
||||||
default:
|
|
||||||
notificationsToRender = notificationsQueued;
|
|
||||||
}
|
|
||||||
|
|
||||||
let scoredAndSortedNotifications = notificationsToRender
|
|
||||||
.map(decorateWithScore)
|
|
||||||
.sort((a, b) => b.score - a.score);
|
|
||||||
|
|
||||||
// We gotta make sure to search notifications before we paginate.
|
|
||||||
// Otherwise we'd just end up searching on the current page, which is bad.
|
|
||||||
if (this.state.query) {
|
|
||||||
scoredAndSortedNotifications = scoredAndSortedNotifications.filter(n => (
|
|
||||||
n.name.toLowerCase().indexOf(this.state.query.toLowerCase()) > -1)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.notificationsApi.newChanges) {
|
|
||||||
const filteredNewChanges = this.props.notificationsApi.newChanges.filter(n => (
|
|
||||||
scoredAndSortedNotifications.some(fn => fn.id === n.id)
|
|
||||||
));
|
|
||||||
if (filteredNewChanges.length > 0) {
|
|
||||||
this.sendWebNotification(filteredNewChanges);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
notifications: scoredAndSortedNotifications,
|
|
||||||
queuedCount: notificationsQueued.length,
|
|
||||||
stagedCount: notificationsStaged.length,
|
|
||||||
closedCount: notificationsClosed.length,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
if (!this.props.authApi.token) {
|
|
||||||
return <Redirect noThrow to={routes.HOME} />
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
fetchNotifications,
|
|
||||||
markAllAsStaged,
|
|
||||||
clearCache,
|
|
||||||
notificationsPermission,
|
|
||||||
loading: isFetchingNotifications,
|
|
||||||
error: fetchingNotificationsError,
|
|
||||||
} = this.props.notificationsApi;
|
|
||||||
const {
|
|
||||||
notifications: scoredAndSortedNotifications,
|
|
||||||
queuedCount,
|
|
||||||
stagedCount,
|
|
||||||
closedCount,
|
|
||||||
} = this.getFilteredNotifications();
|
|
||||||
|
|
||||||
let firstIndex = (this.state.currentPage - 1) * PER_PAGE;
|
|
||||||
let lastIndex = (this.state.currentPage * PER_PAGE);
|
|
||||||
let notificationsOnPage = scoredAndSortedNotifications.slice(firstIndex, lastIndex);
|
|
||||||
let lastPage = Math.ceil(scoredAndSortedNotifications.length / PER_PAGE);
|
|
||||||
let firstNumbered = firstIndex + 1;
|
|
||||||
let lastNumbered = Math.min(lastIndex, scoredAndSortedNotifications.length);
|
|
||||||
|
|
||||||
if (scoredAndSortedNotifications.length === 0) {
|
|
||||||
firstIndex = 0;
|
|
||||||
lastIndex = 0;
|
|
||||||
notificationsOnPage = [];
|
|
||||||
lastPage = 1;
|
|
||||||
firstNumbered = 0;
|
|
||||||
lastNumbered = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stagedTodayCount = this.props.storageApi.getStat('stagedCount')[0];
|
|
||||||
const stagedStatistics = this.props.storageApi.getStat(
|
|
||||||
'stagedCount',
|
|
||||||
this.state.currentTime.clone().startOf('week').subtract(1, 'week'),
|
|
||||||
this.state.currentTime.clone().endOf('week')
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Scene
|
|
||||||
currentTime={this.state.currentTime}
|
|
||||||
stagedStatistics={stagedStatistics}
|
|
||||||
isFirstTimeUser={this.state.isFirstTimeUser}
|
|
||||||
setNotificationsPermission={this.setNotificationsPermission}
|
|
||||||
notificationsPermission={notificationsPermission}
|
|
||||||
queuedCount={queuedCount}
|
|
||||||
stagedCount={stagedCount}
|
|
||||||
closedCount={closedCount}
|
|
||||||
stagedTodayCount={stagedTodayCount || 0}
|
|
||||||
first={firstNumbered}
|
|
||||||
last={lastNumbered}
|
|
||||||
lastPage={lastPage}
|
|
||||||
allNotificationsCount={scoredAndSortedNotifications.length}
|
|
||||||
notifications={notificationsOnPage}
|
|
||||||
query={this.state.query}
|
|
||||||
page={this.state.currentPage}
|
|
||||||
activeStatus={this.state.activeStatus}
|
|
||||||
activeFilter={this.state.activeFilter}
|
|
||||||
onChangePage={this.onChangePage}
|
|
||||||
onLogout={this.onLogout}
|
|
||||||
onSetActiveStatus={this.onSetActiveStatus}
|
|
||||||
onSearch={this.onSearch}
|
|
||||||
onClearQuery={this.onClearQuery}
|
|
||||||
onFetchNotifications={fetchNotifications}
|
|
||||||
onMarkAsRead={this.enhancedOnMarkAsRead}
|
|
||||||
onMarkAllAsStaged={markAllAsStaged}
|
|
||||||
onClearCache={clearCache}
|
|
||||||
onStageThread={this.enhancedOnStageThread}
|
|
||||||
onRestoreThread={this.restoreThread}
|
|
||||||
onRefreshNotifications={this.props.storageApi.refreshNotifications}
|
|
||||||
isSearching={this.state.isSearching}
|
|
||||||
isFetchingNotifications={isFetchingNotifications}
|
|
||||||
fetchingNotificationsError={fetchingNotificationsError || this.state.error}
|
|
||||||
onSetActiveFilter={this.onSetActiveFilter}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const enhance = compose(
|
|
||||||
withStorageProvider,
|
|
||||||
withAuthProvider,
|
|
||||||
withCookiesProvider,
|
|
||||||
withNotificationsProvider
|
|
||||||
);
|
|
||||||
|
|
||||||
export default enhance(NotificationsPage);
|
|
@ -255,7 +255,7 @@ export function BasicPageWrapper ({loggedIn, onLogout, children}) {
|
|||||||
<LoginContainer>
|
<LoginContainer>
|
||||||
<Button to={routes.GUIDE}>{'Guide'}</Button>
|
<Button to={routes.GUIDE}>{'Guide'}</Button>
|
||||||
<Button to={routes.PRICING}>{'Pricing'}</Button>
|
<Button to={routes.PRICING}>{'Pricing'}</Button>
|
||||||
<Button to={routes.REDESIGN_NOTIFICATIONS} css={css`
|
<Button to={routes.NOTIFICATIONS} css={css`
|
||||||
&::after {
|
&::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
export {default as Home} from './Home';
|
export {default as Home} from './Home';
|
||||||
export {default as Notifications} from './Notifications';
|
|
||||||
export {default as NotificationsRedesign} from './NotificationsRedesign';
|
export {default as NotificationsRedesign} from './NotificationsRedesign';
|
||||||
export {default as Login} from './Login';
|
export {default as Login} from './Login';
|
||||||
export {default as Pricing} from './Pricing';
|
export {default as Pricing} from './Pricing';
|
||||||
|
Loading…
Reference in New Issue
Block a user