Pricing page

This commit is contained in:
Nicholas Zuber 2019-11-13 16:46:57 -05:00
parent 1cd8d240c3
commit 52e12bedac
12 changed files with 922 additions and 290 deletions

View File

@ -5,6 +5,7 @@ import { AuthProvider } from './providers/Auth';
import {
Home,
Login,
Pricing,
Notifications,
NotificationsRedesign,
} from './pages';
@ -16,6 +17,7 @@ class App extends Component {
<Router>
<Home path={routes.HOME} />
<Login path={routes.LOGIN} />
<Pricing path={routes.PRICING} />
<Notifications path={routes.NOTIFICATIONS} />
<NotificationsRedesign path={routes.REDESIGN_NOTIFICATIONS} />
</Router>

View File

@ -1,6 +1,7 @@
export default {
HOME: '/',
NOTIFICATIONS: '/notifications',
PRICING: '/pricing',
REDESIGN_NOTIFICATIONS: '/notifications-redesign',
LOGIN: '/login'
};

View File

@ -5,7 +5,12 @@ import styled from '@emotion/styled';
import {css, jsx} from '@emotion/core';
import {Link as RouterLink} from '@reach/router';
import {routes} from '../../constants';
import Logo from '../../components/Logo';
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'
@ -32,9 +37,6 @@ import '../../styles/font.css';
const themeColor = '#27B768';
const ALT_BACKGROUND_COLOR = '#f6f2ed';
const hash = process.env.GIT_HASH ? `#${process.env.GIT_HASH}` : '';
const version = require('../../../package.json').version + hash;
const ProductHuntButton = () => (
<a href="https://www.producthunt.com/posts/meteorite?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-meteorite" target="_blank">
<img
@ -46,26 +48,6 @@ const ProductHuntButton = () => (
</a>
);
const forSmallScreens = rules => `
@media (max-width: 1100px) {
${rules}
}
`;
const forMobile = rules => `
@media (max-width: 800px) {
${rules}
}
`;
const DefaultContainer = styled('div')`
overflow-x: hidden;
* {
font-family: medium-content-sans-serif-font, "Inter UI", system-ui, sans-serif;
font-size: 15px;
}
`;
const Outer = styled('div')`
background: ${p => p.alt ? ALT_BACKGROUND_COLOR : 'none'};
`;
@ -96,37 +78,6 @@ const FlexItem = styled('div')`
flex-wrap: wrap;
`;
const ButtonLink = styled('a')`
cursor: pointer;
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;
padding: 0.125rem 0.75rem;
font-size: 18px;
line-height: 1.75;
border-radius: 5px;
transition: all 0.15s ease-in-out;
&:hover {
background-color: #f4f4f4;
border-color: #f4f4f4;
}
&:active {
background-color: #eee;
border-color: #eee;
}
`;
const Button = styled(RouterLink)`
cursor: pointer;
position: relative;
@ -185,41 +136,6 @@ const HeroButton = styled(MainButton)`
margin-left: 0;
`;
const LogoTitle = styled('span')`
display: inline-block;
font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif;
color: #333333;
font-size: 22px;
font-weight: 800;
cursor: pointer;
user-select: none;
${forMobile(`
display: none;
`)}
`;
const LogoSection = () => (
<div css={css`
display: inline-flex;
justify-content: space-between;
align-items: center;
width: 125px;
${forMobile(`
width: 36px;
`)}
div {
display: inline-block;
margin-right: 8px;
}
`}>
<Logo white size={26} style={{filter: 'invert(0.8)'}} />
<LogoTitle>Meteorite</LogoTitle>
</div>
);
const LoginContainer = styled('div')``;
const FlexBreak = styled('div')`
flex-basis: 100%;
height: 0;
@ -431,145 +347,7 @@ const CompanyPerson = styled('div')`
}
`;
const GroupedLinks = styled(`div`)`
text-align: center;
margin: 8px auto 0;
a {
text-decoration: none;
font-size: 17px;
position: relative;
display: inline-block;
padding: 0.75rem 2.25rem;
border: 1px solid #EAEDF3;
margin-left: -1px;
margin-top: 8px;
transition: all 75ms ease-in-out;
}
a:hover {
background-color: #EAEDF366;
}
a:active {
background-color: #EAEDF3;
}
a:first-of-type {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
a:last-of-type {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
`;
const FooterSubtleText = styled('p')`
margin: 24px auto;
color: #6c757d;
`;
const WorkflowToggle = () => {
const [state, setState] = React.useState(0);
const [imageOpacity, setImageOpacity] = React.useState(1);
const timer = React.useRef();
const easeTimingMs = 200;
const 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
},
];
const activeItem = items[state];
return (
<div css={css`
margin-top: 32px;
display: flex;
flex-direction: row;
${forSmallScreens(`
flex-direction: column;
`)}
`}>
<div css={css`
flex: 1;
${forSmallScreens(`
margin-bottom: 24px;
`)}
`}>
{items.map((item, xid) => (
<div
key={xid}
onClick={() => {
if (xid === state) return;
clearTimeout(timer.current);
setImageOpacity(0);
timer.current = setTimeout(() => {
setState(xid);
setImageOpacity(1);
}, easeTimingMs);
}}
css={css`
border-radius: 8px;
padding: 18px 20px;
margin: 0 32px 8px 0;
cursor: pointer;
background: ${state === xid ? '#ffffff' : 'none'};
transition: all 0.15s ease-in-out;
`}>
<h3 css={css`
font-size: 22px;
line-height: 26px;
margin: 0 auto 4px;
font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif;
font-weight: 500;
`}>{item.title}</h3>
<p css={css`
font-size: 16px;
line-height: 20px;
margin: 0;
`}>{item.description}</p>
</div>
))}
</div>
<div css={css`
position: relative;
flex: 3;
`}>
<img
src={activeItem.image}
css={css`
max-width: 100%;
opacity: ${imageOpacity};
will-change: opacity;
box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,0.175);
transition: opacity ${easeTimingMs}ms linear;
border-radius: 4px;
`} />
</div>
</div>
);
};
export default function Scene ({loggedIn, onLogout, ...props}) {
export default function Scene (props) {
const baseItemOffset = {
scale: 0.1,
top: -185,
@ -577,37 +355,7 @@ export default function Scene ({loggedIn, onLogout, ...props}) {
};
return (
<DefaultContainer>
{/* Header */}
<Container>
<FlexItem css={css`
align-items: center;
display: flex;
justify-content: space-between;
`}>
<LogoSection />
{loggedIn ? (
<LoginContainer>
<Button to={routes.REDESIGN_NOTIFICATIONS} css={css`
&::after {
content: "";
position: absolute;
background: #E91E63;
top: 6px;
right: 6px;
height: 8px;
width: 8px;
border-radius: 100%;
}`}>{'Notifications'}</Button>
<ButtonLink href="#" onClick={onLogout}>{'Logout'}</ButtonLink>
</LoginContainer>
) : (
<LoginContainer>
<MainButton to={routes.LOGIN}>{'Login / Sign up'}</MainButton>
</LoginContainer>
)}
</FlexItem>
</Container>
<BasicPageWrapper {...props}>
{/* Hero */}
<Container css={css`
@ -627,6 +375,7 @@ export default function Scene ({loggedIn, onLogout, ...props}) {
${forMobile(`
display: block;
margin: 0 auto;
width: 300px;
`)}
`}>
<HeroButton css={css`${forMobile(`margin-bottom: 12px;`)}`} to={routes.LOGIN}>{'Login / Sign up'}</HeroButton>
@ -744,7 +493,7 @@ export default function Scene ({loggedIn, onLogout, ...props}) {
<FeatureItem color={'#fd9446'}>
<WbIridescentSvg />
<h3>{'Reasoning'}</h3>
<p>{'We\ll also tell you why you\'re getting each notification.'}</p>
<p>{'We\'ll also tell you why you\'re getting each notification.'}</p>
</FeatureItem>
<FeatureItem color={'#fc46fd'}>
<TimelineSvg />
@ -776,26 +525,6 @@ export default function Scene ({loggedIn, onLogout, ...props}) {
<FlexBreak height={40} />
</Container>
</Outer>
{/* Footer */}
<Container column>
<Logo white size={42} style={{filter: 'invert(0.8)', margin: '0 auto'}} />
<Title css={css`font-size: 26px; margin: 12px auto;`}>{'Manage your notifications.'}</Title>
<FlexBreak height={20} />
<GroupedLinks>
<a target="_blank" href="https://github.com/nickzuber/meteorite/issues">Donate</a>
<a target="_blank" href="https://github.com/nickzuber/meteorite/issues">Feedback</a>
<a target="_blank" href="https://github.com/nickzuber/meteorite/commits/master">Changelog</a>
</GroupedLinks>
<GroupedLinks>
<a target="_blank" href="https://github.com/nickzuber/meteorite">GitHub</a>
<a target="_blank" href="https://twitter.com/nick_zuber">Twitter</a>
</GroupedLinks>
<FlexBreak height={20} />
<FooterSubtleText>
{`© 2019 Nick Zuber Meteorite v${version}`}
</FooterSubtleText>
</Container>
</DefaultContainer>
</BasicPageWrapper>
);
};

View File

@ -0,0 +1,131 @@
/** @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 {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';
const WorkflowToggle = () => {
const [state, setState] = React.useState(0);
const [imageOpacity, setImageOpacity] = React.useState(1);
const timer = React.useRef();
const easeTimingMs = 200;
const 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
},
];
const activeItem = items[state];
return (
<div css={css`
margin-top: 32px;
display: flex;
flex-direction: row;
${forSmallScreens(`
flex-direction: column;
`)}
`}>
<div css={css`
flex: 1;
${forSmallScreens(`
margin-bottom: 24px;
`)}
`}>
{items.map((item, xid) => (
<div
key={xid}
onClick={() => {
if (xid === state) return;
clearTimeout(timer.current);
setImageOpacity(0);
timer.current = setTimeout(() => {
setState(xid);
setImageOpacity(1);
}, easeTimingMs);
}}
css={css`
border-radius: 8px;
padding: 18px 20px;
margin: 0 32px 8px 0;
cursor: pointer;
background: ${state === xid ? '#ffffff' : 'none'};
transition: all 0.15s ease-in-out;
`}>
<h3 css={css`
font-size: 22px;
line-height: 26px;
margin: 0 auto 4px;
font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif;
font-weight: 500;
`}>{item.title}</h3>
<p css={css`
font-size: 16px;
line-height: 20px;
margin: 0;
`}>{item.description}</p>
</div>
))}
</div>
<div css={css`
position: relative;
flex: 3;
`}>
<img
src={activeItem.image}
css={css`
max-width: 100%;
opacity: ${imageOpacity};
will-change: opacity;
box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,0.175);
transition: opacity ${easeTimingMs}ms linear;
border-radius: 4px;
`} />
</div>
</div>
);
};
export default WorkflowToggle;

View File

@ -345,12 +345,9 @@ export default function Scene ({
prev: readTodayLastWeekCount
});
readStatistics = readStatistics.map(n => parseInt(n, 10) || 0);
// const lastWeekStats = readStatistics.slice(0, 7);
// const thisWeekStats = readStatistics.slice(7);
const lastWeekStats = [0, 3, 7, 2, 4, 5, 0];
const thisWeekStats = [0, 2, 8, 4, 5, null, null];
readStatistics = readStatistics.map(n => parseInt(n, 10));
const lastWeekStats = readStatistics.slice(0, 7);
const thisWeekStats = readStatistics.slice(7);
const percentageDeltaToday = getPercentageDelta(counts.cur, counts.prev);
const highestRepoReadCount = Object.values(reposReadCounts).reduce((h, c) => Math.max(h, c), 0);

View File

@ -711,7 +711,7 @@ export function ProfileSection ({user, onLogout}) {
onLogout();
setMenuShow(false);
}}>
<h2>Sign out</h2>
<h2>Logout</h2>
<p>Log off your account and return to home page</p>
</optimized.div>
</InteractionMenu>

443
src/pages/Pricing/Scene.js Normal file
View File

@ -0,0 +1,443 @@
/** @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 ItemPng from '../../images/screenshots/item.png';
import ItemTwoPng from '../../images/screenshots/item-2.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 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: 4rem;
${forSmallScreens(`
padding-left: 2.5rem;
padding-right: 2.5rem;
`)}
${forMobile(`
margin-bottom: 2.5rem;
`)}
`;
const LightContainer = styled(Container)`
${forSmallScreens(`
padding-left: 0;
padding-right: 0;
`)}
${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 0;
font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif;
font-weight: 500;
${forMobile(`
font-size: 46px;
line-height: 54px;
margin: 0 auto 6px;
`)}
`;
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 LeftTitle = styled(Title)`
margin: 0 0 4px;
text-align: left;
font-size: 34px;
line-height: 34px;
`;
const LeftSubtitle = styled(Subtitle)`
margin: 0 0 12px;
text-align: left;
font-size: 20px;
line-height: 23px;
`;
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(`
margin: 0 auto;
width: 100%;
`)}
`;
const PricingContainer = styled('div')`
position: relative;
background: ${ALT_BACKGROUND_COLOR};
width: 70%;
margin-right: 0;
margin-left: auto;
min-height: 100px;
border-radius: 4px;
padding: 28px 24px;
text-align: left;
${forMobile(`
width: 100%;
`)}
h4 {
margin: -2px 0 0;
font-size: 20px;
color: #26b768;
font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif;
}
p {
font-size: 14px;
line-height: 16px;
margin: 8px auto 0;
color: #6c757d;
}
`;
const Price = styled(Title)(p => `
font-size: ${p.crossed ? 32 : 52}px;
line-height: ${p.crossed ? 32 : 54}px;
text-decoration: ${p.crossed ? 'line-through' : 'unset'};
display: inline-block;
margin-right: 8px;
text-align: left;
`);
const Badge = styled('span')`
font-size: 14px;
line-height: 16px;
text-align: left;
background: #26b768;
color: #fff;
font-weight: 600;
padding: 2px 6px;
border-radius: 4px;
vertical-align: super;
display: inline-block;
`;
const FloatingLinkBox = styled('div')`
background: rgb(255, 254, 253);
flex: 1;
position: relative;
box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,0.075);
border-radius: 4px;
padding: 16px 24px;
margin: 0 24px 0 0;
${forMobile(`
margin: 0 0 24px 0;
`)}
h4 {
margin: 0;
color: #47494b;
font-size: 18px;
}
p {
font-size: 16px;
line-height: 18px;
margin: 6px auto;
color: #6c757d;
}
a {
text-decoration: none;
font-size: 16px;
color: #00A0F5;
transition: all 200ms ease;
}
a:hover {
color: #0886c9;
}
a::after {
content: " →"
}
`;
const HorizontalFlexContainer = styled('div')`
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: baseline;
margin-top: 1rem;
`;
const FAQItem = styled('div')`
flex: 1 0 34%;
${forMobile(`
flex: 1 0 51%;
`)}
flex-direction: column;
display: flex;
align-items: left;
justify-content: center;
margin-bottom: 1rem;
padding: 0 12px;
h3 {
font-size: 21px;
justify-content: center;
line-height: 25px;
margin: 18px 0 8px;
font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif;
font-weight: 600;
letter-spacing: 0.7px;
}
p {
font-size: 18px;
justify-content: center;
line-height: 22px;
margin: 0 0 12px;
color: #6c757d;
a {
font-size: 18px;
justify-content: center;
line-height: 22px;
}
}
`;
export default function Scene (props) {
return (
<BasicPageWrapper {...props}>
{/* Pricing */}
<Container css={css`
${forMobile(`
flex-direction: column;
`)}
`}>
<HeroLeft>
<HeroTitle>
{'Simple pricing.'}<br />
{'Great value.'}</HeroTitle>
<HeroSubtitle>{'Get started for absolutely free. No trials needed.'}</HeroSubtitle>
<FlexBreak />
</HeroLeft>
<HeroRight>
<PricingContainer>
<Price>{'$0/mo'}</Price>
<Badge>{'Great deal'}</Badge>
<h4>{'Unlimited everything'}</h4>
<p>{'A simple and better way to manage GitHub notifications for more \
engineers who want to be more productive.'}</p>
</PricingContainer>
</HeroRight>
</Container>
<Container css={css`${forMobile(`flex-direction: column;`)}`}>
<FloatingLinkBox>
<h4>{'Pay what you want.'}</h4>
<p>{'Donate to the engineers working on this project.'}</p>
<a target="_blank" href="https://donorbox.org/meteorite">{'Visit now'}</a>
</FloatingLinkBox>
<FloatingLinkBox>
<h4>{'Want to contribute?'}</h4>
<p>{'All of the code is open sourced and free to edit.'}</p>
<a target="_blank" href="https://github.com/nickzuber/meteorite">{'Check it out'}</a>
</FloatingLinkBox>
</Container>
{/* FAQ */}
<Container column>
<LeftTitle css={css`padding-left: 12px;`}>{'Still have questions?'}</LeftTitle>
<LeftSubtitle css={css`padding-left: 12px;`}>{'Here are some commonly asked questions.'}</LeftSubtitle>
<LightContainer column>
<HorizontalFlexContainer>
<FAQItem>
<h3>{'What exactly is Meteorite?'}</h3>
<p>{'Meteorite is an app designed around prioritizing and organizing your \
GitHub notifications. It has a variety of features to help keep you \
focused on the notifications that matter to you, and see the most important \
updates at the top of your list by keeping track of the thread context.'}</p>
<p>{'Basically, Meteorite is the GitHub notifications of your dreams.'}</p>
</FAQItem>
<FAQItem>
<h3>{'Why does this exist?'}</h3>
<p>{'GitHub notifications right now can be chaotic and noisy, which end up\
making them pretty useless. Meteorite solves this problem by filtering, \
sorting, and adding context to the notifications you would normally get.'}</p>
<p>{'This project originated from me personally getting sick and tired of the current\
GitHub notifications system, so I took a crack and making it better.'}</p>
</FAQItem>
<FAQItem>
<h3>{'Can I try it for free?'}</h3>
<p>{'Absolutely Meteorite is completely free and open source.'}</p>
</FAQItem>
<FAQItem>
<h3>{'Can I host an instance of Meteorite myself?'}</h3>
<p>{'Sure feel free to fork/contribute/self-host anything with this project. Since this project\
is completely serverless, you can host your own instance for free using platforms like '}
<a href="https://zeit.co/">{'Zeit'}</a>{', '}<a href="https://surge.sh/">{'Surge'}</a>{', \
or anything in between.'}</p>
</FAQItem>
<FAQItem>
<h3>{'I like this project. How can I contribute?'}</h3>
<p>{'You can contribute by submitting bug reports, feature requests, code that implements any \
of those things, or you can help by '}<a href="https://donorbox.org/meteorite">{'donating'}</a>{' to the project itself.'}</p>
</FAQItem>
<FAQItem>
<h3>{'Where can I ask more questions?'}</h3>
<p>{'You can either personally reach out to me on '}<a href="https://twitter.com/nick_zuber">{'Twitter'}</a>{' or \
leave your question on the official '}<a href="https://github.com/nickzuber/meteorite">{'GitHub repository'}</a>{' as an issue.'}</p>
</FAQItem>
</HorizontalFlexContainer>
</LightContainer>
</Container>
</BasicPageWrapper>
);
};

View File

@ -0,0 +1,30 @@
import React from 'react';
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';
class PricingPage extends React.Component {
onLogout = () => {
// Remove cookie and invalidate token on client.
this.props.cookiesApi.removeCookie(OAUTH_TOKEN_COOKIE);
this.props.authApi.invalidateToken();
}
render () {
return (
<Scene
loggedIn={!!this.props.authApi.token}
onLogout={this.onLogout}
/>
);
}
};
const enhance = compose(
withAuthProvider,
withCookiesProvider
);
export default enhance(PricingPage);

297
src/pages/common/index.js Normal file
View File

@ -0,0 +1,297 @@
/** @jsx jsx */
import React from 'react';
import styled from '@emotion/styled';
import {css, jsx} from '@emotion/core';
import {navigate, Link as RouterLink} from '@reach/router';
import {routes} from '../../constants';
import Logo from '../../components/Logo';
import '../../styles/gradient.css';
import '../../styles/font.css';
const themeColor = '#27B768';
const hash = process.env.GIT_HASH ? `#${process.env.GIT_HASH}` : '';
const version = require('../../../package.json').version + hash;
export const forSmallScreens = rules => `
@media (max-width: 1100px) {
${rules}
}
`;
export const forMobile = rules => `
@media (max-width: 800px) {
${rules}
}
`;
const DefaultContainer = styled('div')`
overflow-x: hidden;
* {
font-family: medium-content-sans-serif-font, "Inter UI", system-ui, sans-serif;
font-size: 15px;
}
`;
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.0rem;
padding-right: 2.0rem;
`)}
${forMobile(`
margin-bottom: 2.5rem;
`)}
`;
const FlexItem = styled('div')`
flex: ${(({flex = 1}) => flex)};
align-items: center;
display: flex;
flex-wrap: wrap;
`;
const ButtonLink = styled('a')`
cursor: pointer;
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;
padding: 0.125rem 0.75rem;
font-size: 18px;
line-height: 1.75;
border-radius: 5px;
transition: all 0.15s ease-in-out;
&:hover {
background-color: #f4f4f4;
border-color: #f4f4f4;
}
&:active {
background-color: #eee;
border-color: #eee;
}
`;
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 LogoTitle = styled('span')`
display: inline-block;
font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif;
color: #333333;
font-size: 22px;
font-weight: 800;
cursor: pointer;
user-select: none;
margin-top: 4px;
${forMobile(`
display: none;
`)}
`;
const LogoSection = () => (
<div onClick={() => navigate(routes.HOME)} css={css`
display: inline-flex;
justify-content: space-between;
align-items: center;
width: 125px;
${forMobile(`
width: 36px;
`)}
div {
display: inline-block;
margin-right: 8px;
}
`}>
<Logo white size={26} style={{filter: 'invert(0.8)'}} />
<LogoTitle>Meteorite</LogoTitle>
</div>
);
const LoginContainer = styled('div')``;
const FlexBreak = styled('div')`
flex-basis: 100%;
height: 0;
min-height: ${p => p.height || 0}px;
`;
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 GroupedLinks = styled(`div`)`
text-align: center;
margin: 8px auto 0;
a {
text-decoration: none;
font-size: 17px;
position: relative;
display: inline-block;
padding: 0.75rem 2.25rem;
border: 1px solid #EAEDF3;
margin-left: -1px;
margin-top: 8px;
transition: all 75ms ease-in-out;
}
a:hover {
background-color: #EAEDF366;
}
a:active {
background-color: #EAEDF3;
}
a:first-of-type {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
a:last-of-type {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
`;
const FooterSubtleText = styled('p')`
margin: 24px auto;
color: #6c757d;
`;
export function BasicPageWrapper ({loggedIn, onLogout, children}) {
return (
<DefaultContainer>
{/* Header */}
<Container>
<FlexItem css={css`
align-items: center;
display: flex;
justify-content: space-between;
`}>
<LogoSection />
{loggedIn ? (
<LoginContainer>
<Button to={routes.PRICING}>{'Pricing'}</Button>
<Button to={routes.REDESIGN_NOTIFICATIONS} css={css`
&::after {
content: "";
position: absolute;
background: #E91E63;
top: 6px;
right: 6px;
height: 8px;
width: 8px;
border-radius: 100%;
}`}>{'Notifications'}</Button>
<ButtonLink href="#" onClick={onLogout}>{'Logout'}</ButtonLink>
</LoginContainer>
) : (
<LoginContainer>
<Button to={routes.PRICING}>{'Pricing'}</Button>
<MainButton to={routes.LOGIN}>{'Login / Sign up'}</MainButton>
</LoginContainer>
)}
</FlexItem>
</Container>
{children}
{/* Footer */}
<Container column>
<Logo white size={42} style={{filter: 'invert(0.8)', margin: '0 auto'}} />
<Title css={css`font-size: 26px; margin: 12px auto;`}>{'Manage your notifications.'}</Title>
<FlexBreak height={20} />
<GroupedLinks>
<a target="_blank" href="https://donorbox.org/meteorite">Donate</a>
<a target="_blank" href="https://github.com/nickzuber/meteorite/issues">Feedback</a>
<a target="_blank" href="https://github.com/nickzuber/meteorite/commits/master">Changelog</a>
</GroupedLinks>
<GroupedLinks>
<a target="_blank" href="https://github.com/nickzuber/meteorite">GitHub</a>
<a target="_blank" href="https://twitter.com/nick_zuber">Twitter</a>
</GroupedLinks>
<FlexBreak height={20} />
<FooterSubtleText>
{`© 2019 Nick Zuber Meteorite v${version}`}
</FooterSubtleText>
</Container>
</DefaultContainer>
);
};

View File

@ -2,3 +2,4 @@ export {default as Home} from './Home';
export {default as Notifications} from './Notifications';
export {default as NotificationsRedesign} from './NotificationsRedesign';
export {default as Login} from './Login';
export {default as Pricing} from './Pricing';

View File

@ -6,7 +6,7 @@ const {Provider, Consumer} = React.createContext();
class AuthProvider extends React.Component {
state = {
token: this.props.cookiesApi.getCookie(OAUTH_TOKEN_COOKIE) || 'TOKEN'
token: this.props.cookiesApi.getCookie(OAUTH_TOKEN_COOKIE)
}
setToken = token => {

View File

@ -84,6 +84,7 @@ line[stroke="#666"] {
a {
text-underline-position: under;
transition: all 200ms ease;
}
.react-tooltip {