From 9380afbeb9fa2f3c1cc7227ae1dc96c9f1ad6cb0 Mon Sep 17 00:00:00 2001 From: Nicholas Zuber Date: Mon, 27 Jan 2020 18:54:41 -0500 Subject: [PATCH] Deprecate /notifications-redesign to /notifications --- src/App.js | 10 +- src/pages/Home/Scene.js | 1200 +++++++++++----------------- src/pages/Home/Scene.new.js | 552 ------------- src/pages/Home/index.js | 2 +- src/pages/Login/index.js | 2 +- src/pages/Notifications/Scene.js | 1286 ------------------------------ src/pages/Notifications/index.js | 466 ----------- src/pages/common/index.js | 2 +- src/pages/index.js | 1 - 9 files changed, 459 insertions(+), 3062 deletions(-) delete mode 100644 src/pages/Home/Scene.new.js delete mode 100644 src/pages/Notifications/Scene.js delete mode 100644 src/pages/Notifications/index.js diff --git a/src/App.js b/src/App.js index 8b2b365..e2c44e9 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import amplitude from 'amplitude-js'; import { + Redirect, Router, Location, LocationProvider @@ -12,7 +13,6 @@ import { Login, Pricing, Guide, - Notifications, NotificationsRedesign, } from './pages'; @@ -41,6 +41,10 @@ function PageTracker ({location}) { return null; } +function RedirectShell () { + return ; +} + class App extends Component { render() { return ( @@ -54,8 +58,8 @@ class App extends Component { - - + + diff --git a/src/pages/Home/Scene.js b/src/pages/Home/Scene.js index c92d4b4..0fe2345 100644 --- a/src/pages/Home/Scene.js +++ b/src/pages/Home/Scene.js @@ -3,23 +3,29 @@ import React from 'react'; import styled from '@emotion/styled'; import {css, jsx} from '@emotion/core'; -import {navigate, Link as RouterLink} from '@reach/router'; +import {Link as RouterLink} from '@reach/router'; import {routes} from '../../constants'; -import Logo from '../../components/Logo'; -import ConfettiSection from '../../components/Confetti'; +import { + BasicPageWrapper, + forSmallScreens, + forMobile +} from '../common'; +import WorkflowToggle from './WorkflowToggle'; -import headerPng from '../../images/safari-header.png'; -import pwaHeaderPng from '../../images/screenshots/pwa-demo.png'; -import regularScreenshotPng from '../../images/traditional-screenshot.png'; -import scoreScreenshotPng from '../../images/screenshots/screenshot-score.png'; -import iPhoneXMockupPng from '../../images/screenshots/iphone-x-mockup.png'; -import iPhoneScreenshotPng from '../../images/screenshots/iphone-x.png'; -import { ReactComponent as MentionSvg } from '../../images/svg/mention.svg'; -import { ReactComponent as GoodTeamSvg } from '../../images/svg/good-team.svg'; -import { ReactComponent as ProcessgSvg } from '../../images/svg/process.svg'; -import { ReactComponent as MobileSvg } from '../../images/svg/mobile.svg'; +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 {InteractionMenu, Card} from '../NotificationsRedesign/redesign/ui'; +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'; @@ -28,194 +34,285 @@ import FacebookLogo from '../../images/logos/facebook-logo.png'; import '../../styles/gradient.css'; import '../../styles/font.css'; -const hash = process.localEnv.GIT_HASH ? `#${process.localEnv.GIT_HASH}` : ''; -const version = require('../../../package.json').version + hash; +const themeColor = '#27B768'; +const ALT_BACKGROUND_COLOR = '#f6f2ed'; -const themeColor = '#457cff'; +const ProductHuntButton = () => ( + + Meteorite - Smarter GitHub notifications. | Product Hunt Embed + +); -const WIDTH_FOR_MEDIUM_SCREENS = '1100px'; -const WIDTH_FOR_SMALL_SCREENS = '800px'; - -const HomeInteractionMenu = styled(InteractionMenu)` - position: fixed; - z-index: 10; - left: 86px !important; - top: 86px !important; -`; - -const PageContainer = styled('div')` - overflow: hidden; - background: #fcf8f3; - position: relative; - min-height: calc(100vh - 84px); - height: 100%; - width: 100%; - display: block; - overflow: hidden; -`; - -const FixedContainer = styled('div')` - position: fixed; - top: 0; - left: 0; - right: 0; - z-index: 10; +const Outer = styled('div')` + background: ${p => p.alt ? ALT_BACKGROUND_COLOR : 'none'}; `; const Container = styled('div')` position: relative; - background: rgb(252, 248, 243); - width: 960px; - margin: 0 auto; - display: flex; - justify-content: space-between; - align-items: center; box-sizing: border-box; - padding: 12px 24px; - @media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) { - width: 100%; + 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 NewTag = styled('span')` - align-self: center; - margin-bottom: 8px; - padding: 1px 3px; - background: rgb(235, 87, 87); - color: white; - border-radius: 3px; - margin-left: 6px; - margin-bottom: 6px; - font-size: 9px; - line-height: 1.3; - vertical-align: text-top; - text-transform: uppercase; - font-weight: 600; - letter-spacing: 0.5px; +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 LogoTitle = styled('span')` - display: inline-block; - font-size: 16px; - font-weight: 600; - cursor: pointer; - user-select: none; +const HeroButton = styled(MainButton)` + font-size: 22px; + margin: -4px 8px 0; + margin-left: 0; `; -const Header = styled('h1')` - position: relative; - text-align: left; - width: 680px; - max-width: 680px; - margin: 0; - z-index: 2; - font-size: 72px; - line-height: 78px; +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; - @media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) { - width: 100%; - font-size: 4rem; - line-height: 4.25rem; - } + text-align: center; `; -const SubHeader = styled(Header)` - hyphens: auto; - font-size: 24px; - line-height: 26px; - font-weight: 600; - width: 680px; - max-width: 680px; - margin: 0 auto; +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; - color: #b3b0a9; - @media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) { + 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%; - font-size: 20px; - line-height: 26px; - } + text-align: center; + `)} `; -const MainItemContainer = styled(Container)` - margin: 88px auto 32px; - align-items: flex-start; - flex-direction: column; - @media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) { - margin-top: 0; - } -`; - -const ItemWrapper = styled('div')` - display: flex; - justify-content: space-between; - align-items: center; - flex-direction: row; - margin-bottom: 28px; - width: inherit; - @media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) { - flex-direction: column; - flex-flow: column-reverse; - } -`; - -const ItemNumber = styled('span')` - font-size: 11rem; - font-weight: 500; - @media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) { +const HeroRight = styled(FlexItem)` + flex-grow: 1; + width: 50%; + ${forMobile(` display: none; - } + `)} `; -const ItemContainer = styled('div')` +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; - width: 540px; - margin: 0; + 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; - padding: 12px 24px; - flex-direction: column; - @media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) { - width: 100%; - } + margin-top: 4rem; + ${forMobile(` + flex-direction: column; + max-width: 300px; + margin: 0 auto; + `)} `; -const ItemHeader = styled(Header)` - position: relative; - text-align: left; - width: inherit; - margin: 0; - z-index: 2; - font-size: 62px; - line-height: 64px; - margin: 0 0 12px; - font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif; - font-weight: 500; - @media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) { - margin-top: 16px; - font-size: 42px; - line-height: 48px; - } +const HorizontalFlexContainer = styled('div')` + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + margin-top: 4rem; `; -const ItemSubHeader = styled(SubHeader)` - width: inherit; -`; - -const HorizontalListItem = styled('div')` +const FeatureItem = styled('div')(({color}) => ` flex: 1; - border-right: ${props => props.last ? '0px' : '1px'} solid rgba(214, 212, 209, 0.3); - padding: 0 32px; - @media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) { - border-right: none; - border-bottom: ${props => props.last ? '0px' : '1px'} solid rgba(214, 212, 209, 0.3); - padding-bottom: 32px; - margin-bottom: 32px; + ${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; @@ -244,611 +341,212 @@ const CompanyPerson = styled('div')` span { display: block; padding: 0 16px; - font-size: 14px; - line-height: 18px; + font-size: 16px; + line-height: 20px; color: #37352f80; } `; -const IconLink = styled('span')` - position: relative; - cursor: pointer; - display: inline-flex; - justify-content: center; - align-items: center; - z-index: 1; - user-select: none; - height: 40px; - width: 40px; - transition: all 150ms ease; - i { - font-size: 16px; - color: inherit; - } - &:before { - content: ""; - transition: all 150ms ease; - background: #BFC5D122; - border-radius: 100%; - display: block; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - transform: scale(0); - } - &:hover:before { - transform: scale(1); - } - &:active:before { - background: #BFC5D144; - } -`; - -const DemoScreenshotHeader = styled('img')` - background: #f7f6f3; - width: 960px; - max-width: 960px; - display: block; - box-shadow: rgba(0, 0, 0, 0.15) 0px 10px 20px, rgb(245, 245, 245) 0px -1px 0px; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - z-index: 3; - @media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) { - width: 100%; - } -`; - -const DemoScreenshot = styled(DemoScreenshotHeader)` - border-top-left-radius: 0; - border-top-right-radius: 0; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; -`; - -const IPhoneScreenshotContainer = styled('img')` - position: absolute; - bottom: -32px; - right: -32px; - width: 214px; - max-width: 214px; - min-width: 107px; - display: block; - background: none; - z-index: 5; - @media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) { - display: none; - } -`; - -const IPhoneScreenshot = styled(IPhoneScreenshotContainer)` - bottom: -48px; - z-index: 4; - transform: scale(0.88); -`; - -const SmallText = styled('p')` - margin: 0; - font-size: 12px; - font-weight: 500; - display: inline-block; - color: #37352f; - z-index: 2; - @media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) { - display: none; - } -`; - -const SmallLink = styled('a')` - margin: 0; - font-size: 12px; - font-weight: 600; - display: inline-block; - color: #37352f; - z-index: 2; - text-decoration: none; - text-underline-position: initial; - transition: all 200ms ease; - &:hover { - text-decoration: underline; - } - @media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) { - display: none; - } -`; - -export default function Scene ({loggedIn, onLogout, ...props}) { - const [showBorder, setShowBorder] = React.useState(false); - const [menu, openMenu] = React.useState(false); - - React.useEffect(() => { - const body = window.document.querySelector('body'); - const hideMenu = () => openMenu(false); - // For mobile `touchend` - body.addEventListener('click', hideMenu); - return () => body.removeEventListener('click', hideMenu); - }, []); - - // React.useEffect(() => { - // const PAGE_OFFSET = 100; - // const onScroll = () => { - // if (window.pageYOffset >= PAGE_OFFSET) { - // setShowBorder(true); - // } else if (window.pageYOffset < PAGE_OFFSET) { - // setShowBorder(false); - // } - // }; - // window.addEventListener('scroll', onScroll); - // return () => window.removeEventListener('scroll', onScroll); - // }, []); +export default function Scene (props) { + const baseItemOffset = { + scale: 0.1, + top: -185, + left: 20 + }; return ( - - + + {/* Hero */} + - + + {'Manage your notifications.'} + {'Meteorite helps organize, filter, and prioritize your\ + GitHub notifications to make your life easier'} +
- - Meteorite -
-
- Desktop - iOS & Android -
- {loggedIn ? ( - <> - Notifications - - Redesign - new - - Sign out - - ) : ( - <> - Sign in - - )} -
-
- {/* @TODO implement the menu */} - openMenu(true)}> - - -
- - - - - -
- Desktop -
- - {loggedIn ? ( - <> -
- Notifications -
-
- - Redesign - new - -
-
- Sign out -
- - ) : ( -
- Sign in -
- )} -
-
- - -
- - - {/* Header for larger devices */} -
- {'Control your GitHub notifications'} -
- {/* Header for small devices */} -
- {'Control'}
{'your GitHub notifications'} -
- -
- - {'Prioritize the tasks that keep you and your team most productive by organizing your notifications'} - -
-
- navigate(routes.REDESIGN_NOTIFICATIONS)}>{'Let\'s get started'} - - {'Learn more'} - - -
-
- - - {'View and contribute on GitHub'} - - - - {'Free and open sourced'} - -
-
+ width: 300px; + `)} + `}> + {'Login / Sign up'} +
-
-
- - - - -
+ + + + + +
- -
- - - {'Hear what folks have to say'} - - - {'Loved by other human beings, just like you'} - -
- - {`So good! I love the importance sorting!`} - - - - {'— Mike Grabowski'}
- {'Software Architect, React Native'} -
-
-
- - {`I've been using it for a bit and it's so useful, especially if you use GitHub for work.`} - - - - {'— Trevor Suarez'}
- {'Robin, Backend Software Engineer'} -
-
-
- - {`Awww sh*t, nice.`} - - - - {'— Chris Walker'}
- {'Forward, Software Engineer'} -
-
-
-
-
+ {/* Testimonials */} + + + {'Hear what others are saying'} + {'Loved by other human beings, just like you'} + + + {`So good! I love the importance sorting!`} + + + + {'— Mike Grabowski'}
+ {'Software Architect, React Native'} +
+
+
+ + {`I've been using it for a bit and it's so useful, especially if you use GitHub for work.`} + + + + {'— Trevor Suarez'}
+ {'Robin, Backend Software Engineer'} +
+
+
+ + {`Awww sh*t, nice.`} + + + + {'— Chris Walker'}
+ {'Forward, Software Engineer'} +
+
+
+
- - - {1} - - - {'An assistant for your GitHub notifications'} - - - {'Do the same things you do everyday, just a whole lot easier'} - - - - + {/* Lifecycle */} + + + + {'The Notification Lifecycle'} + {'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.'} + + + {'Start using Meteorite absolutely free'} + {'Login / Sign up'} + + + -
- - -
-
- - - - {2} - - - {'Your time matters, so things stay simple'} - - - {'Simply sign in and start working — no complicated or weirdly intrusive set up needed'} - - - - - -
- - -
-
- - - - {3} - - - {'Work anywhere and everywhere'} - - - {'You love accessibility and so does Meteorite — available on iOS, Android, web, and desktop as an installable PWA'} - - - - - -
- - -
-
- - - - - + {/* Features */} + + + {'Features built for getting things done.'} + {'All of the features of Meteorite are specifically designed'}
+ {'for optimizing your workday'}
+ + + +

{'Serverless'}

+

{'Any notification scoring and storing is done completely offline.'}

+
+ + +

{'Desktop Notifications'}

+

{'Get notified when we do – ability to turn on desktop notifications.'}

+
+ + +

{'Auto Sorting'}

+

{'Keep your most important notifications at the top of the list.'}

+
+ + +

{'Filter Noise'}

+

{'Any notifications that don\'t directly involve you are hidden.'}

+
+ + + +

{'Dead Simple'}

+

{'No integrations – just log in and start working.'}

+
+ + +

{'Live Updates'}

+

{'All of your notifications are processed in real time.'}

+
+ + +

{'Reasoning'}

+

{'We\'ll also tell you why you\'re getting each notification.'}

+
+ + +

{'Statistics'}

+

{'Better understand how you work with data visualizations.'}

+
+
- + {/* Closer */} + + + + {'Better notifications for everyone.'} + + {'Login / Sign up'} + + + + + + + + + ); }; diff --git a/src/pages/Home/Scene.new.js b/src/pages/Home/Scene.new.js deleted file mode 100644 index 0fe2345..0000000 --- a/src/pages/Home/Scene.new.js +++ /dev/null @@ -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 = () => ( - - Meteorite - Smarter GitHub notifications. | Product Hunt Embed - -); - -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 ( - - - {/* Hero */} - - - {'Manage your notifications.'} - {'Meteorite helps organize, filter, and prioritize your\ - GitHub notifications to make your life easier'} - -
- {'Login / Sign up'} - -
-
- - - - - -
- - {/* Testimonials */} - - - {'Hear what others are saying'} - {'Loved by other human beings, just like you'} - - - {`So good! I love the importance sorting!`} - - - - {'— Mike Grabowski'}
- {'Software Architect, React Native'} -
-
-
- - {`I've been using it for a bit and it's so useful, especially if you use GitHub for work.`} - - - - {'— Trevor Suarez'}
- {'Robin, Backend Software Engineer'} -
-
-
- - {`Awww sh*t, nice.`} - - - - {'— Chris Walker'}
- {'Forward, Software Engineer'} -
-
-
-
-
- - {/* Lifecycle */} - - - - {'The Notification Lifecycle'} - {'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.'} - - - {'Start using Meteorite absolutely free'} - {'Login / Sign up'} - - - - - {/* Features */} - - - {'Features built for getting things done.'} - {'All of the features of Meteorite are specifically designed'}
- {'for optimizing your workday'}
- - - -

{'Serverless'}

-

{'Any notification scoring and storing is done completely offline.'}

-
- - -

{'Desktop Notifications'}

-

{'Get notified when we do – ability to turn on desktop notifications.'}

-
- - -

{'Auto Sorting'}

-

{'Keep your most important notifications at the top of the list.'}

-
- - -

{'Filter Noise'}

-

{'Any notifications that don\'t directly involve you are hidden.'}

-
- - - -

{'Dead Simple'}

-

{'No integrations – just log in and start working.'}

-
- - -

{'Live Updates'}

-

{'All of your notifications are processed in real time.'}

-
- - -

{'Reasoning'}

-

{'We\'ll also tell you why you\'re getting each notification.'}

-
- - -

{'Statistics'}

-

{'Better understand how you work with data visualizations.'}

-
-
-
- - {/* Closer */} - - - - {'Better notifications for everyone.'} - - {'Login / Sign up'} - - - - - - - - -
- ); -}; diff --git a/src/pages/Home/index.js b/src/pages/Home/index.js index 593f9e0..bce2e21 100644 --- a/src/pages/Home/index.js +++ b/src/pages/Home/index.js @@ -3,7 +3,7 @@ import { compose } from 'recompose'; import { withAuthProvider } from '../../providers/Auth'; import { withCookiesProvider } from '../../providers/Cookies'; import { OAUTH_TOKEN_COOKIE } from '../../constants/cookies'; -import Scene from './Scene.new'; +import Scene from './Scene'; class HomePage extends React.Component { onLogout = () => { diff --git a/src/pages/Login/index.js b/src/pages/Login/index.js index 167f096..49f7b76 100644 --- a/src/pages/Login/index.js +++ b/src/pages/Login/index.js @@ -24,7 +24,7 @@ class LoginPage extends React.Component { render () { if (this.props.authApi.token) { - return + return } return ( diff --git a/src/pages/Notifications/Scene.js b/src/pages/Notifications/Scene.js deleted file mode 100644 index c471c10..0000000 --- a/src/pages/Notifications/Scene.js +++ /dev/null @@ -1,1286 +0,0 @@ -import React from 'react'; -import moment from 'moment'; -// import {VictoryPie, VictoryChart} from "victory"; -import {Link} from "@reach/router"; -import styled from 'react-emotion'; -import Icon from '../../components/Icon'; -import Logo from '../../components/Logo'; -import LoadingIcon from '../../components/LoadingIcon'; -import ErrorMessage from '../../components/ErrorMessage'; -import {routes} from '../../constants'; -import {Filters} from '../../constants/filters'; -import {withOnEnter, withTooltip} from '../../enhance'; -import {Status} from '../../constants/status'; -import {Reasons, Badges} from '../../constants/reasons'; -import '../../styles/gradient.css'; - -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-script-url */ - -function getColorFromFilter (activeFilter) { - switch (activeFilter) { - case Filters.ASSIGNED: - return '#f12c3f'; - case Filters.PARTICIPATING: - return '#00d19a'; - case Filters.COMMENT: - return '#24292e'; - case Filters.REVIEW_REQUESTED: - return '#00A0F5'; - default: - return '#24292e'; - } -} - -function getWeekday (day) { - switch (day) { - case 0: - return 'Sunday'; - case 1: - return 'Monday'; - case 2: - return 'Tuesday'; - case 3: - return 'Wednesday'; - case 4: - return 'Thursday'; - case 5: - return 'Friday'; - case 6: - return 'Saturday'; - } -} - -function stringOfType (type) { - switch (type) { - case 'PullRequest': - return 'pull request'; - case 'Issue': - return 'issue'; - default: - return 'task'; - } -} - -export function getMessageFromReasons (reasons, type) { - switch (reasons[reasons.length - 1].reason) { - case Reasons.ASSIGN: - return 'You were assigned this ' + stringOfType(type); - 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 for this ' + stringOfType(type); - case Reasons.SUBSCRIBED: - return 'There was an update and you\'re subscribed'; - case Reasons.OTHER: - default: - return 'Something was updated'; - } -} - -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'; - - 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'; -} - -const UnofficialReleaseTag = styled('span')({ - color: 'white', - position: 'absolute', - left: '21px', - bottom: '0px', - fontSize: '9px', - background: '#f42839', - fontWeight: '800', - padding: '2px 4px', - borderRadius: '4px', - textTransform: 'uppercase', -}); - -const FixedContainer = styled('div')({ - height: '80%', - maxWidth: 270, - display: 'block', - position: 'relative' -}); - -const InlineBlockContainer = styled('div')({ - 'div': { - display: 'inline-block' - } -}); - -const NotificationsContainer = styled('div')({ - position: 'relative', - margin: '0 auto', - padding: 0, - width: '100%', - display: 'flex', - flexDirection: 'row', - overflowX: 'hidden', - boxSizing: 'border-box' -}); - -const NavigationContainer = styled('div')({ - position: 'fixed', - top: 0, - boxSizing: 'border-box', - margin: '0 auto', - width: '100%', - height: 60, - color: 'hsla(0,0%,100%,.75)', - paddingBottom: '12px', - paddingTop: '12px', - zIndex: '100', -}); - -const GeneralOptionsContainer = styled(NavigationContainer)({ - position: 'relative', - zIndex: '1', - height: 'initial', - minHeight: 60, - width: '95%', - margin: 0, - padding: '8px 16px', - paddingTop: 18, - flex: '0 0 50px', - 'button': { - display: 'inline-flex', - margin: 0 - } -}); - -const Sidebar = styled('div')({ - flex: '0 0 300px', - padding: '32px 20px', - paddingRight: 0, - display: 'flex', - justifyContent: 'center', -}); - -const SidebarLink = styled('a')({}, ({active, color}) => ({ - textAlign: 'left', - userSelect: 'none', - margin: '0 auto 5px', - position: 'relative', - cursor: 'pointer', - borderRadius: 4, - alignItems: 'center', - padding: '0 14px', - height: 40, - width: 200, - fontSize: '12px', - fontWeight: 600, - letterSpacing: 0.5, - textTransform: 'capitalize', - textDecoration: 'none', - transition: 'background 0.12s ease-in-out', - display: 'flex', - background: active ? color : 'none', - color: active ? '#fff' : '#202124', - ':before': { - content: '""', - transition: 'all 150ms ease', - background: 'rgba(190, 197, 208, 0.25)', - borderRadius: 4, - display: 'block', - top: 0, - bottom: 0, - right: 0, - left: 0, - position: 'absolute', - transform: 'scale(0)' - }, - ':hover:before': { - transform: active ? 'scale(0)' : 'scale(1)', - }, - ':active:before': { - background: 'rgba(190, 197, 208, 0.5)' - }, - 'div': { - marginRight: 5 - } -})); - -const Notifications = styled('div')({ - flex: 1, -}); - -const NavTab = styled('a')({ - position: 'relative', - textTransform: 'capitalize', - userSelect: 'none', - borderRadius: 4, - textDecoration: 'none', - fontWeight: '500', - fontSize: '14px', - textAlign: 'left', - opacity: 0.6, - padding: '20px 32px', - paddingLeft: '16px', - width: '150px', - display: 'inline-block', - margin: 0, - transition: 'all 150ms ease', - ':hover': { - background: 'rgba(190, 197, 208, 0.25)', - }, -}, ({ number }) => ({ - ':after': number > 0 && { - content: `"${number}"`, - color: '#ffffff', - background: '#a8a8a9', - fontSize: '10px', - verticalAlign: 'text-top', - padding: '1px 8px', - borderRadius: '4px', - marginLeft: '6px', - display: 'inline-block', - } -}), ({ active, color, number }) => active && ({ - color, - opacity: 1, - ':before': { - content: '""', - position: 'absolute', - background: color, - height: '3px', - width: '90%', - bottom: '0', - left: '5%', - borderTopLeftRadius: '4px', - borderTopRightRadius: '4px', - }, - ':after': number > 0 && { - content: `"${number}"`, - color: '#ffffff', - background: color, - fontSize: '10px', - verticalAlign: 'text-top', - padding: '1px 8px', - borderRadius: '4px', - marginLeft: '6px', - display: 'inline-block', - } -})); - -const Tab = styled('button')({ - position: 'relative', - userSelect: 'none', - cursor: 'pointer', - border: 0, - outline: 'none', - background: 'none', - height: 40, - width: 40, - borderRadius: '100%', - margin: '0 auto', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - ':before': { - content: "''", - transition: 'all 150ms ease', - background: 'rgba(190, 197, 208, 0.25)', - borderRadius: '100%', - display: 'block', - height: 40, - width: 40, - position: 'absolute', - transform: 'scale(0)' - }, - ':hover:before': { - transform: 'scale(1)', - }, - ':active:before': { - background: 'rgba(190, 197, 208, 0.5)' - } -}, ({disabled}) => disabled && ({ - background: 'none !important', - opacity: 0.35, - cursor: 'default', - ':hover:before': { - transform: 'scale(0) !important', - }, - ':active:before': { - background: 'none !important' - } -})); - -const SearchField = styled('div')({ - float: 'left', - textAlign: 'left', - width: '50%', - boxShadow: '0 4px 6px rgba(50,50,93,.11), 0 1px 3px rgba(0,0,0,.08)', - margin: '0 auto', - background: 'hsla(0,0%,100%,.125)', - borderRadius: '4px', - alignItems: 'center', - padding: 0, - height: '36px', - fontSize: '13px', - textDecoration: 'none', - transition: 'all 0.06s ease-in-out', - display: 'inline-flex', - ':focus-within': { - background: '#fff' - } -}); - -const Message = styled('div')({ - display: 'block', - textAlign: 'center', - marginTop: 24 * 5, - 'p': { - paddingTop: 24, - userSelect: 'none', - display: 'block', - margin: 0 - } -}); - -const LoaderContainer = styled('div')({ - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - height: '100%' -}); - -const SearchInput = styled('input')({ - flex: 1, - textAlign: 'left', - margin: '0 auto', - background: 'none', - padding: 0, - height: '36px', - color: '#fff', - fontSize: '13px', - textDecoration: 'none', - display: 'inline-flex', - border: '0', - outline: 'none', - ':focus': { - color: '#202124' - } -}); -const EnhancedSearchInput = withOnEnter(SearchInput); - -const NotificationRow = styled('tr')({ - position: 'relative', - display: 'flex', - alignItems: 'center', - textAlign: 'left', - width: '100%', - borderRadius: 4, - margin: '0 auto', - padding: '8px 16px', - transition: 'all 0.1s ease-in-out', - boxSizing: 'border-box', - ':hover': { - background: '#f9f9f9', - // boxShadow: '0 4px 6px rgba(50,50,93,.11), 0 1px 3px rgba(0,0,0,.08)', - zIndex: 10 - } -}); - -const NotificationTab = styled(Tab)({ - display: 'inline-flex', - margin: 0, -}); - -const Timestamp = styled('span')({ - position: 'relative', - margin: 0, - marginLeft: 10, - fontSize: 11, - opacity: 0.5, -}); - -const ReasonMessage = styled(Timestamp)({ - -}); - -const NotificationTitle = styled('span')({ - position: 'relative', - display: 'block' -}, ({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: 500, - marginLeft: 10, - fontSize: 14 -}); - -const PRIssue = styled(Repository)({ - fontWeight: 400, -}, ({after}) => ({ - ':after': { - content: `"#${after}"`, - fontSize: 13, - opacity: .3, - marginLeft: 5 - } -})); - -const Table = styled('table')({ - width: '96%', - minWidth: 970, - 'td': { - display: 'inline-block' - } -}); - -const TableItem = styled('td')({ - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', -}, ({width, flex}) => ({ - width, - flex -})); - -const SmallLink = styled('a')({ - display: 'block', - marginRight: 10, - cursor: 'pointer', - fontSize: 10, - lineHeight: '20px', - fontWeight: 400, - textDecoration: 'underline', - transition: 'all 0.12s ease-in-out', - ':hover': { - opacity: 0.75 - } -}); - -const BarGraphDiv = styled('div')({ - position: 'relative', - height: 100, - ':after': { - content: `"Notifications Read"`, - position: 'absolute', - textAlign: 'left', - fontSize: 16, - top: -67, - left: -23, - fontWeight: 500 - } -}); - -const FauxGridLine = styled('div')({ - position: 'absolute', - width: '100%', - height: 1, - background: '#f3f3f3', -}, ({offsetY, tick, showDecimals}) => ({ - bottom: offsetY, - ':after': tick !== false ? { - content: showDecimals - ? `"${tick.toFixed(2).replace('.00', '')}"` - : `"${Math.round(tick)}"`, - position: 'absolute', - top: '-6px', - left: '-30px', - width: '20px', - textAlign: 'right', - } : {} -})); - -const Legend = styled('div')({ - position: 'absolute', - textAlign: 'left', - top: -35, - left: -23, -}); - -const LegendItem = styled('div')({ - position: 'relative', - display: 'inline-block', - fontSize: 12, - marginRight: 15, -}, ({color}) => ({ - ':before': { - content: '" "', - background: color, - display: 'inline-block', - height: 12, - width: 12, - borderRadius: 2, - marginRight: 5, - verticalAlign: 'middle' - } -})); - -function BarGraph ({children, numLines, max, ...props}) { - const height = 100; - const gapSize = height / (numLines - 1); - const yAxisTickOffset = gapSize / 100 * max; - // Only show decimals if we have to, e.g. the steps are under 1. - const showDecimals = yAxisTickOffset * numLines < 2; - return ( - - - Last week - This week - - {new Array(numLines).fill(0).map((_, i) => ( - 0 || i === 0 ? ( - i % 2 === 0 ? i * yAxisTickOffset : false - ) : ( - false - )} - /> - ))} - {children} - - ); -} - -const Separator = styled('div')({ - position: 'relative', - background: '#f3f3f3', - width: '100%', - height: 1, - margin: '30px 0' -}); - -const BarContainer = styled('div')({ - position: 'absolute', - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-evenly', - width: '100%', - height: 100, - alignItems: 'flex-end', -}, ({opacity, hide}) => ({ - 'div': { - background: opacity ? '#f2f9ff' : undefined, - opacity: hide ? 0 : 1 - } -})); - -const stripe_size = 3; -const Bar = styled('div')({ - position: 'relative', - width: 30, - minHeight: 5, - background: '#00d19a', - borderRadius: 4, -}, ({height, active, label, visible = true, bottomTick}) => ({ - height, - background: !visible - ? 'none' - : active && false - ? `repeating-linear-gradient(45deg, #28abf0, #28abf0 ${stripe_size}px, #28abf055 ${stripe_size}px, #28abf055 ${stripe_size * 2}px)` - : '#28abf0', - ':after': label ? { - content: `"${label}"`, - position: 'absolute', - left: '-20px', - width: '70px', - textAlign: 'center', - bottom: '-25px', - color: '#3f464c', - } : {}, - ':before': bottomTick ? { - content: '" "', - position: 'absolute', - background: '#f3f3f3', - width: '1px', - height: '8px', - bottom: '-8px', - left: '14px', - } : {}, -})); - -const PageCount = styled('div')({ - fontSize: 12, - margin: '12px auto', - width: '100%', - textAlign: 'center', - color: 'rgb(32, 33, 36)' -}); - -const NotificationIconWrapper = styled('div')({ - background: '#22303f', - borderRadius: 4, - transform: 'scale(.65)' -}); - -const EnhancedBar = withTooltip(Bar); -const EnhancedTab = withTooltip(Tab); -const EnhancedNavTab = withTooltip(NavTab); -const EnhancedNotificationTab = withTooltip(NotificationTab); -const EnhancedSidebarLink = withTooltip(SidebarLink); -const EnhancedIconHot = withTooltip(Icon.Hot); -const EnhancedIconTimer = withTooltip(Icon.Timer); -const EnhancedIconConvo = withTooltip(Icon.Convo); - -// @TODO if GitHub ever fixes their API, we can use `reasons` to know when -// a PR/Issue merges/closes/etc. -function getPRIssueIcon (type, _reasons) { - const scale = 1.0; - switch (type) { - case 'PullRequest': - return ( - - - - ); - case 'Issue': - return ( - - - - ); - default: - return null; - } -} - -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 -}) { - const loading = isSearching || isFetchingNotifications; - const isFirstPage = page === 1; - const isLastPage = page === lastPage; - - 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 - } - - 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); - - // Trim off the weekends. - lastWeekStats = lastWeekStats.slice(1, -1); - thisWeekStats = thisWeekStats.slice(1, -1); - - return ( -
- -
- { - onSetActiveStatus(Status.QUEUED); - onSetActiveFilter(Filters.PARTICIPATING); - }} - /> - beta - - - event.target.select()} - type="text" - placeholder="Search for notifications" - onEnter={onSearch} - /> - {isSearching && } - -
- home -
- use new redesign -
-
-
- sign out -
-
-
-
-
- - -
-

- - {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 deleted file mode 100644 index 61173cf..0000000 --- a/src/pages/Notifications/index.js +++ /dev/null @@ -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 - } - - 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 ( - - ); - } -}; - -const enhance = compose( - withStorageProvider, - withAuthProvider, - withCookiesProvider, - withNotificationsProvider -); - -export default enhance(NotificationsPage); diff --git a/src/pages/common/index.js b/src/pages/common/index.js index 58e3759..82322b4 100644 --- a/src/pages/common/index.js +++ b/src/pages/common/index.js @@ -255,7 +255,7 @@ export function BasicPageWrapper ({loggedIn, onLogout, children}) { -