Run prettier on ./src

This commit is contained in:
Nicholas Zuber 2020-11-02 18:45:10 -05:00
parent 1bae149082
commit 693084125c
33 changed files with 873 additions and 647 deletions

View File

@ -1,47 +1,38 @@
import React, { Component } from 'react';
import React, {Component} from 'react';
import amplitude from 'amplitude-js';
import {
Redirect,
Router,
Location,
LocationProvider
} from "@reach/router";
import { routes } from './constants';
import { AuthProvider } from './providers/Auth';
import {
Home,
Login,
Pricing,
Guide,
NotificationsRedesign,
} from './pages';
import {Redirect, Router, Location, LocationProvider} from '@reach/router';
import {routes} from './constants';
import {AuthProvider} from './providers/Auth';
import {Home, Login, Pricing, Guide, NotificationsRedesign} from './pages';
// Initalize Amplitude for this session.
amplitude.init('752af9db0250fe93d507f42362a2977d');
function gtag () {
function gtag() {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push(arguments);
}
function gaTrack (options) {
function gaTrack(options) {
gtag('config', 'UA-154218045-1', options);
}
// Effectively track each new page.
function PageTracker ({location}) {
React.useEffect(() => {
gaTrack({
page_location: location,
page_path: location.pathname
});
}, [location]);
function PageTracker({location}) {
React.useEffect(
() => {
gaTrack({
page_location: location,
page_path: location.pathname
});
},
[location]
);
return null;
}
function RedirectShell () {
function RedirectShell() {
return <Redirect noThrow to={routes.NOTIFICATIONS} />;
}

View File

@ -2,7 +2,7 @@
import {css, jsx} from '@emotion/core';
function color (rand) {
function color(rand) {
// const c = [
// '#4caf50',
// '#e91e63',
@ -10,94 +10,135 @@ function color (rand) {
// '#e6d435',
// '#B424E6',
// ];
const c = [
'#B424E6',
'#1ACA6B',
'#E62465',
'#FFCD4C',
'#4C84FF',
'#E9519A',
];
const c = ['#B424E6', '#1ACA6B', '#E62465', '#FFCD4C', '#4C84FF', '#E9519A'];
return c[~~(c.length * rand)];
}
function opacity (xid, max) {
function opacity(xid, max) {
// Max value (best possible score).
const cap = 1;
// Best value to base distribution on (the center value).
const best = Math.floor(max / 2);
// How spread out the distribution is from the best value.
const distribution = 8;
const result = Math.min(1, cap * Math.pow(
Math.E,
(-0.5) * (Math.pow(xid - best, 2) / (Math.pow(distribution, 2)))
));
const result = Math.min(
1,
cap *
Math.pow(
Math.E,
-0.5 * (Math.pow(xid - best, 2) / Math.pow(distribution, 2))
)
);
return Math.max(1 - result, 0.05) * 1.75;
}
function range (min, max, rand) {
function range(min, max, rand) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(rand * (max - min + 1)) + min;
}
function getConfetti (seed) {
function getConfetti(seed) {
const stroke = color(seed);
const rotation = ~~(seed * 180);
const svgs = [
(
<svg css={css`transform: rotate(${rotation}deg);`} width="12" height="9" viewBox="0 0 12 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 0L11.1962 9H0.803848L6 0Z" fill={stroke}/>
</svg>
),
(
<svg css={css`transform: rotate(${rotation}deg);`} width="12" height="9" viewBox="0 0 12 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6 0L11.1962 9H0.803848L6 0Z"
fill={stroke}
/>
</svg>
),
(
<svg css={css`transform: rotate(${rotation}deg);`} width="14" height="18" viewBox="0 0 14 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 2C0.517508 6.06139 6.6799 11.5502 2 16"
stroke={stroke}
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
),
(
<svg css={css`transform: rotate(${rotation}deg);`} width="8" height="13" viewBox="0 0 8 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M4 0L8 6.5L4 13L0 6.5L4 0Z"
fill={stroke}
/>
</svg>
),
(
<svg css={css`transform: rotate(${rotation}deg);`} width="8" height="14" viewBox="0 0 8 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M8 6.6393C8 9.44331 2.33469 14 0.577298 14C-1.1801 14 1.63591 9.44331 1.63591 6.6393C1.63591 3.83529 -0.119245 0 1.63815 0C3.39555 0 8 3.83529 8 6.6393Z"
fill={stroke}
/>
</svg>
),
(
<svg css={css`transform: rotate(${rotation}deg);`} width="10" height="12" viewBox="0 0 10 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M9.15408 5.477C8.15168 8.67191 7 13.5 5.15408 11.5C3.30816 9.5 2.90284 9.34138 1.15517 5.47695C-2.00009 -1.49991 3.99732 1.50011 5.15408 3.977C6.15408 1.47707 11.5 -2 9.15408 5.477Z"
fill={stroke}/>
</svg>
),
(
<svg css={css`transform: rotate(${rotation}deg);`} width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="5" cy="5" r="5" fill={stroke}/>
</svg>
)
<svg
css={css`
transform: rotate(${rotation}deg);
`}
width="12"
height="9"
viewBox="0 0 12 9"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M6 0L11.1962 9H0.803848L6 0Z" fill={stroke} />
</svg>,
<svg
css={css`
transform: rotate(${rotation}deg);
`}
width="12"
height="9"
viewBox="0 0 12 9"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M6 0L11.1962 9H0.803848L6 0Z" fill={stroke} />
</svg>,
<svg
css={css`
transform: rotate(${rotation}deg);
`}
width="14"
height="18"
viewBox="0 0 14 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 2C0.517508 6.06139 6.6799 11.5502 2 16"
stroke={stroke}
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>,
<svg
css={css`
transform: rotate(${rotation}deg);
`}
width="8"
height="13"
viewBox="0 0 8 13"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M4 0L8 6.5L4 13L0 6.5L4 0Z" fill={stroke} />
</svg>,
<svg
css={css`
transform: rotate(${rotation}deg);
`}
width="8"
height="14"
viewBox="0 0 8 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8 6.6393C8 9.44331 2.33469 14 0.577298 14C-1.1801 14 1.63591 9.44331 1.63591 6.6393C1.63591 3.83529 -0.119245 0 1.63815 0C3.39555 0 8 3.83529 8 6.6393Z"
fill={stroke}
/>
</svg>,
<svg
css={css`
transform: rotate(${rotation}deg);
`}
width="10"
height="12"
viewBox="0 0 10 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.15408 5.477C8.15168 8.67191 7 13.5 5.15408 11.5C3.30816 9.5 2.90284 9.34138 1.15517 5.47695C-2.00009 -1.49991 3.99732 1.50011 5.15408 3.977C6.15408 1.47707 11.5 -2 9.15408 5.477Z"
fill={stroke}
/>
</svg>,
<svg
css={css`
transform: rotate(${rotation}deg);
`}
width="10"
height="10"
viewBox="0 0 10 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="5" cy="5" r="5" fill={stroke} />
</svg>
];
// css={css`transform: rotate(${rotation}deg);`}
@ -111,33 +152,84 @@ const Confetti = ({offset, row, seed, index, max}) => {
const fade = opacity(index, max);
offset -= 320;
return (
<div css={css`
z-index: 1;
opacity: ${fade};
transform: scale(1.2);
position: absolute;
left: ${offset}px;
top: ${topDelta}px;
`}
<div
css={css`
z-index: 1;
opacity: ${fade};
transform: scale(1.2);
position: absolute;
left: ${offset}px;
top: ${topDelta}px;
`}
>
{getConfetti(seed)}
</div>
);
}
};
export default function ConfettiSection ({spacing = 82, amount = 18}) {
export default function ConfettiSection({spacing = 82, amount = 18}) {
const row = new Array(amount).fill(0).map((_, i) => i * spacing);
return (
<div css={css`
z-index: 0;
`}>
{row.map((offset, i) => <Confetti index={i} row={0} offset={offset} seed={Math.random()} max={amount} />)}
{row.map((offset, i) => <Confetti index={i} row={1} offset={offset} seed={Math.random()} max={amount} />)}
{row.map((offset, i) => <Confetti index={i} row={2} offset={offset} seed={Math.random()} max={amount} />)}
{row.map((offset, i) => <Confetti index={i} row={3} offset={offset} seed={Math.random()} max={amount} />)}
{row.map((offset, i) => <Confetti index={i} row={4} offset={offset} seed={Math.random()} max={amount} />)}
{row.map((offset, i) => <Confetti index={i} row={5} offset={offset} seed={Math.random()} max={amount} />)}
<div
css={css`
z-index: 0;
`}
>
{row.map((offset, i) => (
<Confetti
index={i}
row={0}
offset={offset}
seed={Math.random()}
max={amount}
/>
))}
{row.map((offset, i) => (
<Confetti
index={i}
row={1}
offset={offset}
seed={Math.random()}
max={amount}
/>
))}
{row.map((offset, i) => (
<Confetti
index={i}
row={2}
offset={offset}
seed={Math.random()}
max={amount}
/>
))}
{row.map((offset, i) => (
<Confetti
index={i}
row={3}
offset={offset}
seed={Math.random()}
max={amount}
/>
))}
{row.map((offset, i) => (
<Confetti
index={i}
row={4}
offset={offset}
seed={Math.random()}
max={amount}
/>
))}
{row.map((offset, i) => (
<Confetti
index={i}
row={5}
offset={offset}
seed={Math.random()}
max={amount}
/>
))}
</div>
);
}

View File

@ -1,19 +1,21 @@
import React from 'react';
import curve from './curve.svg';
export default function Curve ({ style, ...props }) {
export default function Curve({style, ...props}) {
return (
<div style={{
background: `url(${curve}) center bottom`,
position: 'absolute',
bottom: '0',
left: '50%',
transform: 'translateX(-50%)',
width: '190vw',
paddingBottom: '4.5%',
backgroundSize: 'cover',
marginBottom: '-1px',
...style
}} />
<div
style={{
background: `url(${curve}) center bottom`,
position: 'absolute',
bottom: '0',
left: '50%',
transform: 'translateX(-50%)',
width: '190vw',
paddingBottom: '4.5%',
backgroundSize: 'cover',
marginBottom: '-1px',
...style
}}
/>
);
}

View File

@ -4,14 +4,12 @@ import styled from '@emotion/styled';
const Message = styled('p')({
color: '#eb3349',
fontWeight: 500,
'a': {
a: {
color: '#eb3349',
fontWeight: 500,
fontWeight: 500
}
});
export default function ErrorMessage ({children, props}) {
return (
<Message {...props}>{children}</Message>
);
export default function ErrorMessage({children, props}) {
return <Message {...props}>{children}</Message>;
}

View File

@ -68,23 +68,30 @@ import pr_closed from './svg/github/pr-closed.svg';
import pr_open from './svg/github/pr-open.svg';
import pr_merged from './svg/github/pr-merged.svg';
const SvgIcon = styled('div')({
position: 'relative',
backgroundSize: 'cover',
fontSize: 14,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}, ({size, icon, opacity, shrink}) => ({
height: size || 24,
width: size || 24,
background: `url(${icon}) center center no-repeat`,
opacity,
transform: shrink ? `scale(${shrink})` : 'inherit'
}));
const SvgIcon = styled('div')(
{
position: 'relative',
backgroundSize: 'cover',
fontSize: 14,
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
},
({size, icon, opacity, shrink}) => ({
height: size || 24,
width: size || 24,
background: `url(${icon}) center center no-repeat`,
opacity,
transform: shrink ? `scale(${shrink})` : 'inherit'
})
);
export default function Icon ({src, ...props}) {
return <SvgIcon {...props} icon={src}>&nbsp;</SvgIcon>
export default function Icon({src, ...props}) {
return (
<SvgIcon {...props} icon={src}>
&nbsp;
</SvgIcon>
);
}
const createIcon = src => props => <Icon {...props} src={src} />;

View File

@ -3,7 +3,7 @@ import loader from './loader.svg';
import loaderAlt from './loader-alt.svg';
import loaderWhite from './loader-white.svg';
export default function LoadingIcon ({ style, size, alt, white, ...props }) {
export default function LoadingIcon({style, size, alt, white, ...props}) {
let url = loader;
if (white) {
url = loaderWhite;
@ -12,13 +12,16 @@ export default function LoadingIcon ({ style, size, alt, white, ...props }) {
}
return (
<div style={{
background: `url(${(url)}) center center no-repeat`,
position: 'relative',
height: size || 100,
width: size || 100,
margin: '0 auto',
...style
}} {...props} />
<div
style={{
background: `url(${url}) center center no-repeat`,
position: 'relative',
height: size || 100,
width: size || 100,
margin: '0 auto',
...style
}}
{...props}
/>
);
}

View File

@ -2,16 +2,21 @@ import React from 'react';
import meteoriteLogo from './icon-gray.png';
import meteoriteLogoWhite from './logo-white.png';
export default function Logo ({ style, size, white = false, ...props }) {
export default function Logo({style, size, white = false, ...props}) {
return (
<div style={{
background: `url(${white ? meteoriteLogoWhite : meteoriteLogo}) center center no-repeat`,
backgroundSize: 'contain',
position: 'relative',
cursor: props.onClick ? 'pointer' : 'default',
height: size,
width: size,
...style
}} {...props} />
<div
style={{
background: `url(${
white ? meteoriteLogoWhite : meteoriteLogo
}) center center no-repeat`,
backgroundSize: 'contain',
position: 'relative',
cursor: props.onClick ? 'pointer' : 'default',
height: size,
width: size,
...style
}}
{...props}
/>
);
}

View File

@ -20,8 +20,11 @@ const Button = styled('a')`
font-size: 1rem;
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;
-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;
`;
const MainButton = styled(Button)`

View File

@ -1,7 +1,7 @@
export const Status = {
QUEUED: 'queued', // Unread
STAGED: 'staged', // Read
CLOSED: 'closed', // Archived
CLOSED: 'closed', // Archived
// Updated naming, support both for bc.
Unread: 'queued',

View File

@ -16,7 +16,7 @@ export const withOnEnter = WrappedComponent => ({onEnter, ...props}) => (
);
class Tooltip extends React.Component {
constructor (props) {
constructor(props) {
super(props);
this.id = ('tooltip-id-' + Math.random()).replace(/\./g, '');
@ -26,7 +26,7 @@ class Tooltip extends React.Component {
tooltipOffsetX: 0,
tooltipOffsetY: 0,
tooltipSpeed: 350
}
};
getTooltipElement = () => document.querySelector(`#${this.id}`);
@ -54,23 +54,28 @@ class Tooltip extends React.Component {
const {width} = tooltipElement.getBoundingClientRect();
const positionStyle = `
top: ${y + targetHeight + window.scrollY + tooltipOffsetY}px;
left: ${x - (width / 2) + (targetWidth / 2) + tooltipOffsetX}px;
${this.props.dark ? `
left: ${x - width / 2 + targetWidth / 2 + tooltipOffsetX}px;
${
this.props.dark
? `
background: #ffffff;
color: #10293c;
` : ''}
`
: ''
}
`;
tooltipElement.setAttribute('style', positionStyle);
this.timeout = setTimeout(() => {
tooltipElement.setAttribute(
'style', `
'style',
`
${positionStyle}
opacity: ${this.props.dark ? 1 : 0.9};
`
);
}, this.props.tooltipSpeed);
}
};
removeTooltip = () => {
clearTimeout(this.timeout);
@ -78,44 +83,48 @@ class Tooltip extends React.Component {
if (tooltipElement) {
tooltipElement.parentNode.removeChild(tooltipElement);
}
}
};
render () {
render() {
return this.props.children({
onMouseEnter: this.onMouseEnter,
onMouseLeave: this.removeTooltip,
onMouseDown: this.removeTooltip,
onMouseDown: this.removeTooltip
});
}
}
export const withTooltip = WrappedComponent => ({
tooltip,
tooltipOffsetX,
tooltipOffsetY,
tooltipSpeed,
...props
}) => (
<Tooltip
dark={props.dark}
message={tooltip}
tooltipOffsetX={tooltipOffsetX}
tooltipOffsetY={tooltipOffsetY}
tooltipSpeed={tooltipSpeed}
>
{mouseEvents => tooltip ? (
tooltip,
tooltipOffsetX,
tooltipOffsetY,
tooltipSpeed,
...props
}) => (
<Tooltip
dark={props.dark}
message={tooltip}
tooltipOffsetX={tooltipOffsetX}
tooltipOffsetY={tooltipOffsetY}
tooltipSpeed={tooltipSpeed}
>
{mouseEvents =>
tooltip ? (
<WrappedComponent {...props} {...mouseEvents} />
) : (
<WrappedComponent {...props} />
)}
</Tooltip>
)
}
</Tooltip>
);
function transformEventsForMobile (props) {
function transformEventsForMobile(props) {
return Object.keys(props).reduce((aux, prop) => {
let propT = prop;
switch (prop) {
case 'onClick': propT = 'onTouchEnd'; break;
case 'onClick':
propT = 'onTouchEnd';
break;
// ...
}
aux[propT] = props[prop];
@ -123,10 +132,8 @@ function transformEventsForMobile (props) {
}, {});
}
function transformEvents (props) {
return isMobile
? transformEventsForMobile(props)
: props;
function transformEvents(props) {
return isMobile ? transformEventsForMobile(props) : props;
}
export const withOptimizedTouchEvents = BaseComponent => props => (

View File

@ -8,11 +8,11 @@ const OfflinePlugin = require('offline-plugin/runtime');
OfflinePlugin.install({
onInstalled: function() {
console.info('Offline content has been installed.')
console.info('Offline content has been installed.');
},
onUpdating: function() {
console.info('Updating offline content.')
console.info('Updating offline content.');
},
onUpdateReady: function() {

View File

@ -5,16 +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 {
BasicPageWrapper,
forSmallScreens,
forMobile
} from '../common';
import {BasicPageWrapper, forSmallScreens, forMobile} from '../common';
import WorkflowToggle from '../Home/WorkflowToggle';
import SettingsPng from '../../images/screenshots/new/settings.png'
import ParticipatingPng from '../../images/screenshots/new/settings-participating.png'
import WatchingPng from '../../images/screenshots/new/settings-watching.png'
import SettingsPng from '../../images/screenshots/new/settings.png';
import ParticipatingPng from '../../images/screenshots/new/settings-participating.png';
import WatchingPng from '../../images/screenshots/new/settings-watching.png';
import '../../styles/gradient.css';
import '../../styles/font.css';
@ -22,16 +18,15 @@ import '../../styles/font.css';
const themeColor = '#27B768';
const ALT_BACKGROUND_COLOR = '#f6f2ed';
const Outer = styled('div')`
background: ${p => p.alt ? ALT_BACKGROUND_COLOR : 'none'};
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'};
flex-direction: ${p => (p.column ? 'column' : 'row')};
max-width: 1080px;
min-height: 100px;
margin: 0 auto;
@ -40,24 +35,22 @@ const Container = styled('div')`
${forSmallScreens(`
padding-left: 2.5rem;
padding-right: 2.5rem;
`)}
${forMobile(`
`)} ${forMobile(`
margin-bottom: 2.5rem;
`)}
`)};
`;
const LightContainer = styled(Container)`
${forSmallScreens(`
padding-left: 0;
padding-right: 0;
`)}
${forMobile(`
`)} ${forMobile(`
margin-bottom: 2.5rem;
`)}
`)};
`;
const FlexItem = styled('div')`
flex: ${(({flex = 1}) => flex)};
flex: ${({flex = 1}) => flex};
align-items: center;
display: flex;
flex-wrap: wrap;
@ -84,8 +77,11 @@ const Button = styled(RouterLink)`
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;
-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;
@ -131,14 +127,15 @@ 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-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')`
@ -155,14 +152,15 @@ const HeroSubtitle = styled('h1')`
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-family: medium-marketing-display-font, Georgia, Cambria, Times New Roman,
Times, serif;
font-weight: 500;
text-align: center;
`;
@ -202,7 +200,7 @@ const HeroLeft = styled(FlexItem)`
width: 100%;
text-align: center;
min-height: unset;
`)}
`)};
`;
const HeroRight = styled(FlexItem)`
@ -211,7 +209,7 @@ const HeroRight = styled(FlexItem)`
${forMobile(`
margin: 0 auto;
width: 100%;
`)}
`)};
`;
const PricingContainer = styled('div')`
@ -226,13 +224,12 @@ const PricingContainer = styled('div')`
text-align: left;
${forMobile(`
width: 100%;
`)}
h4 {
`)} h4 {
margin: -2px 0 0;
font-size: 20px;
color: #26b768;
font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif;
font-family: medium-marketing-display-font, Georgia, Cambria,
Times New Roman, Times, serif;
}
p {
@ -243,14 +240,16 @@ const PricingContainer = styled('div')`
}
`;
const Price = styled(Title)(p => `
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;
@ -269,15 +268,13 @@ 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);
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 {
`)} h4 {
margin: 0;
color: #47494b;
font-size: 18px;
@ -293,14 +290,14 @@ const FloatingLinkBox = styled('div')`
a {
text-decoration: none;
font-size: 16px;
color: #00A0F5;
color: #00a0f5;
transition: all 200ms ease;
}
a:hover {
color: #0886c9;
}
a::after {
content: " →"
content: ' →';
}
`;
@ -316,8 +313,7 @@ const FAQItem = styled('div')`
flex: 1 0 34%;
${forMobile(`
flex: 1 0 51%;
`)}
flex-direction: column;
`)} flex-direction: column;
display: flex;
align-items: left;
justify-content: center;
@ -329,7 +325,8 @@ const FAQItem = styled('div')`
justify-content: center;
line-height: 25px;
margin: 18px 0 8px;
font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif;
font-family: medium-marketing-display-font, Georgia, Cambria,
Times New Roman, Times, serif;
font-weight: 600;
letter-spacing: 0.7px;
}
@ -354,18 +351,20 @@ const DotsBackground = styled('div')`
height: 400px;
width: 100%;
margin-left: -60px;
background: radial-gradient(transparent 50%, #fffefd), \
url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAKElEQVQoU2NkIBIwEqmOgQ4KX715/x/mHDERQbiNGFZTXyGuUKC+rwHAcQwLu0IifQAAAABJRU5ErkJggg==) repeat;
background: radial-gradient(transparent 50%, #fffefd),
\url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAKElEQVQoU2NkIBIwEqmOgQ4KX715/x/mHDERQbiNGFZTXyGuUKC+rwHAcQwLu0IifQAAAABJRU5ErkJggg==)
repeat;
${forMobile(`
display: none;
`)}
`)};
`;
const Checklist = styled('div')`
z-index: 1;
background: #fff;
margin: 0 auto;
box-shadow: rgba(84,70,35,0) 0px 4px 18px, rgba(84,70,35,0.15) 0px 2px 8px;
box-shadow: rgba(84, 70, 35, 0) 0px 4px 18px,
rgba(84, 70, 35, 0.15) 0px 2px 8px;
border-radius: 6px;
min-height: 100px;
min-width: 200px;
@ -384,11 +383,11 @@ const ChecklistItem = styled('span')`
color: #737b83;
&::before {
content: "✓";
content: '✓';
font-size: 18px;
line-height: 24px;
border-radius: 100%;
background: #27B768;
background: #27b768;
color: #fff;
font-weight: 600;
height: 22px;
@ -406,10 +405,11 @@ const GuideContainer = styled('div')`
flex-direction: row;
${forSmallScreens(`
flex-direction: column;
`)}
`)};
`;
const GuideItem = styled('div')(p => `
const GuideItem = styled('div')(
p => `
flex: ${p.flex || 1};
position: relative;
${forSmallScreens(`
@ -437,23 +437,33 @@ const GuideItem = styled('div')(p => `
box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,0.175);
border-radius: 4px;
}
`);
`
);
export default function Scene (props) {
export default function Scene(props) {
return (
<BasicPageWrapper {...props}>
{/* Hero */}
<Container css={css`
${forMobile(`
<Container
css={css`
${forMobile(`
flex-direction: column;
`)}
`}>
`)};
`}
>
<HeroLeft>
<HeroTitle>
{'No installation.'}<br />
{'No installation.'}
<br />
{'Easy setup.'}
</HeroTitle>
<HeroSubtitle css={css`margin-top: -35px;`}>{'You\'re at most a few click away.'}</HeroSubtitle>
</HeroTitle>
<HeroSubtitle
css={css`
margin-top: -35px;
`}
>
{"You're at most a few click away."}
</HeroSubtitle>
<FlexBreak />
</HeroLeft>
<HeroRight>
@ -462,7 +472,7 @@ export default function Scene (props) {
<ChecklistTitle>{'Only a few simple steps.'}</ChecklistTitle>
<ChecklistItem>{'Navigate to your GitHub settings.'}</ChecklistItem>
<ChecklistItem>{'Turn on web notifications.'}</ChecklistItem>
<ChecklistItem>{'And you\'re done.'}</ChecklistItem>
<ChecklistItem>{"And you're done."}</ChecklistItem>
</Checklist>
</HeroRight>
</Container>
@ -472,8 +482,12 @@ export default function Scene (props) {
<Container column>
<FlexBreak height={60} />
<Title>{'Ready. Set. Go.'}</Title>
<Subtitle>{'You only need to flip a few switches before we can organize your notifications\
and make your life easier.'}</Subtitle>
<Subtitle>
{
'You only need to flip a few switches before we can organize your notifications\
and make your life easier.'
}
</Subtitle>
<WorkflowToggle
easeTimingMs={100}
@ -481,30 +495,48 @@ export default function Scene (props) {
{
id: 0,
title: '1. Visit your settings',
description: 'Head over to GitHub and view your account settings in the notifications tab.',
description:
'Head over to GitHub and view your account settings in the notifications tab.',
image: SettingsPng
},
{
id: 1,
title: '2. Enable Participating alerts',
description: 'Turn on "Web and Mobile" notifications for the participating tab.',
description:
'Turn on "Web and Mobile" notifications for the participating tab.',
image: ParticipatingPng
},
{
id: 2,
title: '3. Enable Watching alerts',
description: 'Turn on "Web and Mobile" notifications for the watching tab.',
description:
'Turn on "Web and Mobile" notifications for the watching tab.',
image: WatchingPng
},
}
]}
/>
<FlexBreak height={100} />
<Title css={css`font-size: 32px; line-height: 38px; margin-bottom: 24px;`}>{'Start using Meteorite absolutely free'}</Title>
<HeroButton to={routes.LOGIN} css={css`margin: 0 auto;`}>{'Login / Sign up'}</HeroButton>
<Title
css={css`
font-size: 32px;
line-height: 38px;
margin-bottom: 24px;
`}
>
{'Start using Meteorite absolutely free'}
</Title>
<HeroButton
to={routes.LOGIN}
css={css`
margin: 0 auto;
`}
>
{'Login / Sign up'}
</HeroButton>
<FlexBreak height={80} />
</Container>
</Outer>
</BasicPageWrapper>
);
};
}

View File

@ -1,8 +1,8 @@
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 {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 GuidePage extends React.Component {
@ -10,17 +10,14 @@ class GuidePage extends React.Component {
// Remove cookie and invalidate token on client.
this.props.cookiesApi.removeCookie(OAUTH_TOKEN_COOKIE);
this.props.authApi.invalidateToken();
}
};
render () {
render() {
return (
<Scene
loggedIn={!!this.props.authApi.token}
onLogout={this.onLogout}
/>
<Scene loggedIn={!!this.props.authApi.token} onLogout={this.onLogout} />
);
}
};
}
const enhance = compose(
withAuthProvider,

View File

@ -5,20 +5,16 @@ 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 {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 {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';
@ -35,20 +31,24 @@ const WorkflowToggle = ({easeTimingMs = 200, items}) => {
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;
<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}
@ -68,36 +68,49 @@ const WorkflowToggle = ({easeTimingMs = 200, items}) => {
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>
`}
>
<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;
`}>
<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);
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.175);
transition: opacity ${easeTimingMs}ms linear;
border-radius: 4px;
`} />
`}
/>
</div>
</div>
);

View File

@ -1,8 +1,8 @@
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 {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 HomePage extends React.Component {
@ -10,17 +10,14 @@ class HomePage extends React.Component {
// Remove cookie and invalidate token on client.
this.props.cookiesApi.removeCookie(OAUTH_TOKEN_COOKIE);
this.props.authApi.invalidateToken();
}
};
render () {
render() {
return (
<Scene
loggedIn={!!this.props.authApi.token}
onLogout={this.onLogout}
/>
<Scene loggedIn={!!this.props.authApi.token} onLogout={this.onLogout} />
);
}
};
}
const enhance = compose(
withAuthProvider,

View File

@ -1,11 +1,11 @@
/** @jsx jsx */
import { Link } from "@reach/router";
import {Link} from '@reach/router';
import styled from '@emotion/styled';
import {css, jsx} from '@emotion/core';
import React from 'react';
import { routes } from '../../constants';
import { AuthenticationButton } from '../../components/buttons';
import {routes} from '../../constants';
import {AuthenticationButton} from '../../components/buttons';
import LoadingIcon from '../../components/LoadingIcon';
import ErrorMessage from '../../components/ErrorMessage';
import ConfettiSection from '../../components/Confetti';
@ -19,15 +19,16 @@ const Card = styled('div')`
min-height: 100px;
margin: 32px auto 0;
background: #ffffff;
border: 1px solid #E5E6EB;
box-shadow: rgba(84, 70, 35, 0) 0px 2px 8px, rgba(84,70,35,0.15) 0px 1px 3px;
border: 1px solid #e5e6eb;
box-shadow: rgba(84, 70, 35, 0) 0px 2px 8px,
rgba(84, 70, 35, 0.15) 0px 1px 3px;
border-radius: 6px;
padding: 24px 32px 52px;
`;
const ButtonsContainer = styled('div')`
display: flex;
justifyContent: space-between;
justifycontent: space-between;
position: absolute;
bottom: 24;
left: 48;
@ -53,29 +54,38 @@ const ButtonsContainer = styled('div')`
}
`;
export default function Scene ({ loading, error, loggedOut, ...props }) {
export default function Scene({loading, error, loggedOut, ...props}) {
return (
<div css={css`
position: relative;
overflow: hidden;
background: ${WHITE};
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
max-width: 1420px;
width: 100%;
margin: 0 auto;
`}>
<div
css={css`
position: relative;
overflow: hidden;
background: ${WHITE};
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
max-width: 1420px;
width: 100%;
margin: 0 auto;
`}
>
<ConfettiSection spacing={100} amount={20} />
<Card>
<h3>Authenticate with GitHub</h3>
{error ? (
<React.Fragment>
<p>Log in with GitHub and we'll start organizing and sorting all of your notifications.</p>
<ErrorMessage>Oops, looks like something went wrong. Try again?</ErrorMessage>
<p>
Log in with GitHub and we'll start organizing and sorting all of
your notifications.
</p>
<ErrorMessage>
Oops, looks like something went wrong. Try again?
</ErrorMessage>
<ButtonsContainer>
<Link style={{boxShadow: '0 0 0'}} to={routes.HOME}>Go back</Link>
<Link style={{boxShadow: '0 0 0'}} to={routes.HOME}>
Go back
</Link>
<AuthenticationButton style={{boxShadow: '0 0 0'}} />
</ButtonsContainer>
</React.Fragment>
@ -83,9 +93,14 @@ export default function Scene ({ loading, error, loggedOut, ...props }) {
<LoadingIcon style={{marginTop: 50}} />
) : loggedOut ? (
<React.Fragment>
<p>Log in with GitHub and we'll start organizing and sorting all of your notifications.</p>
<p>
Log in with GitHub and we'll start organizing and sorting all of
your notifications.
</p>
<ButtonsContainer>
<Link style={{boxShadow: '0 0 0'}} to={routes.HOME}>Go back</Link>
<Link style={{boxShadow: '0 0 0'}} to={routes.HOME}>
Go back
</Link>
<AuthenticationButton style={{boxShadow: '0 0 0'}} />
</ButtonsContainer>
</React.Fragment>

View File

@ -1,11 +1,11 @@
/** @jsx jsx */
import { Link } from "@reach/router";
import {Link} from '@reach/router';
import styled from '@emotion/styled';
import {css, jsx} from '@emotion/core';
import React from 'react';
import { routes } from '../../constants';
import { AuthenticationButton } from '../../components/buttons';
import {routes} from '../../constants';
import {AuthenticationButton} from '../../components/buttons';
import LoadingIcon from '../../components/LoadingIcon';
import ErrorMessage from '../../components/ErrorMessage';
@ -27,8 +27,11 @@ const Button = styled(Link)`
font-size: 1rem;
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;
-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;
@ -47,8 +50,9 @@ const Card = styled('div')`
min-height: 100px;
margin: 32px auto 0;
background: #ffffff;
border: 1px solid #E5E6EB;
box-shadow: rgba(84, 70, 35, 0) 0px 2px 8px, rgba(84,70,35,0.15) 0px 1px 3px;
border: 1px solid #e5e6eb;
box-shadow: rgba(84, 70, 35, 0) 0px 2px 8px,
rgba(84, 70, 35, 0.15) 0px 1px 3px;
border-radius: 6px;
padding: 24px 32px 52px;
`;
@ -79,25 +83,34 @@ const ButtonsContainer = styled('div')`
}
`;
export default function Scene ({ loading, error, loggedOut, ...props }) {
export default function Scene({loading, error, loggedOut, ...props}) {
return (
<div css={css`
position: relative;
overflow: hidden;
background: radial-gradient(transparent 50%, #fffefd), url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAKElEQVQoU2NkIBIwEqmOgQ4KX715/x/mHDERQbiNGFZTXyGuUKC+rwHAcQwLu0IifQAAAABJRU5ErkJggg==) repeat;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100%;
margin: 0 auto;
`}>
<div
css={css`
position: relative;
overflow: hidden;
background: radial-gradient(transparent 50%, #fffefd),
url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAKElEQVQoU2NkIBIwEqmOgQ4KX715/x/mHDERQbiNGFZTXyGuUKC+rwHAcQwLu0IifQAAAABJRU5ErkJggg==)
repeat;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100%;
margin: 0 auto;
`}
>
<Card>
<h3>Authenticate with GitHub</h3>
{error ? (
<React.Fragment>
<p>Log in with GitHub and we'll start organizing and sorting all of your notifications.</p>
<ErrorMessage>Oops, looks like something went wrong. Try again?</ErrorMessage>
<p>
Log in with GitHub and we'll start organizing and sorting all of
your notifications.
</p>
<ErrorMessage>
Oops, looks like something went wrong. Try again?
</ErrorMessage>
<ButtonsContainer>
<Button to={routes.HOME}>Go back</Button>
<AuthenticationButton />
@ -107,9 +120,14 @@ export default function Scene ({ loading, error, loggedOut, ...props }) {
<LoadingIcon style={{marginTop: 50}} />
) : loggedOut ? (
<React.Fragment>
<p>Log in with GitHub and we'll start organizing and sorting all of your notifications.</p>
<p>
Log in with GitHub and we'll start organizing and sorting all of
your notifications.
</p>
<ButtonsContainer>
<Button style={{boxShadow: '0 0 0'}} to={routes.HOME}>Go back</Button>
<Button style={{boxShadow: '0 0 0'}} to={routes.HOME}>
Go back
</Button>
<AuthenticationButton />
</ButtonsContainer>
</React.Fragment>

View File

@ -9,7 +9,7 @@ export default class TokenHandler extends React.Component {
this.props.onSetLoading(true);
fetch(`https://meteorite-gatekeeper.herokuapp.com/authenticate/${code}`)
.then(response => response.json())
.then(({ token, error }) => {
.then(({token, error}) => {
this.props.onSetLoading(false);
if (error) {
this.props.onSetError(true);
@ -20,7 +20,7 @@ export default class TokenHandler extends React.Component {
}
}
render () {
render() {
return null;
}
}

View File

@ -1,30 +1,30 @@
import React from 'react';
import { Redirect } from '@reach/router';
import { compose } from 'recompose';
import { withAuthProvider } from '../../providers/Auth';
import { withNotificationsProvider } from '../../providers/Notifications';
import { withCookiesProvider } from '../../providers/Cookies';
import {Redirect} from '@reach/router';
import {compose} from 'recompose';
import {withAuthProvider} from '../../providers/Auth';
import {withNotificationsProvider} from '../../providers/Notifications';
import {withCookiesProvider} from '../../providers/Cookies';
import TokenHandler from './TokenHandler';
import Scene from './Scene.new';
import { routes } from '../../constants';
import {routes} from '../../constants';
class LoginPage extends React.Component {
state = {
loading: false,
error: null
}
};
onSetLoading = loading => {
this.setState({ loading });
}
this.setState({loading});
};
onSetError = error => {
this.setState({ error });
}
this.setState({error});
};
render () {
render() {
if (this.props.authApi.token) {
return <Redirect noThrow to={routes.NOTIFICATIONS} />
return <Redirect noThrow to={routes.NOTIFICATIONS} />;
}
return (

View File

@ -4,7 +4,7 @@ import {default as RedesignScene} from './redesign/Scene';
/* eslint-disable jsx-a11y/anchor-is-valid */
/* eslint-disable no-script-url */
export default function Scene ({
export default function Scene({
onMarkAsRead,
fetchingNotificationsError,
...props

View File

@ -49,25 +49,28 @@ const BaseBlob = styled('div')`
`;
const Blob1 = styled(BaseBlob)`
background: url(${(B1)}) center center no-repeat;
background: url(${B1}) center center no-repeat;
animation: ${blobFrames1} 25s infinite;
`;
const Blob2 = styled(BaseBlob)`
background: url(${(B2)}) center center no-repeat;
background: url(${B2}) center center no-repeat;
animation: ${blobFrames2} 15s infinite;
`;
const Header = styled('h1')(({dark}) => `
const Header = styled('h1')(
({dark}) => `
text-align: center;
letter-spacing: -0.25px;
font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif;
font-weight: 500;
font-size: 20px;
color: ${dark ? '#ffffff' : '#131212'};
`);
`
);
const Byline = styled('p')(({dark}) => `
const Byline = styled('p')(
({dark}) => `
text-align: center;
font-family: medium-content-sans-serif-font,Inter UI,system-ui,sans-serif;
font-weight: 500;
@ -77,9 +80,11 @@ const Byline = styled('p')(({dark}) => `
font-size: 16px;
line-height: 16px;
color: ${dark ? '#667386' : '#9d9b97'};
`);
`
);
const Container = styled('div')(({opacity}) => `
const Container = styled('div')(
({opacity}) => `
position: relative;
background: none;
height: 550px;
@ -90,9 +95,10 @@ const Container = styled('div')(({opacity}) => `
overflow: hidden;
opacity: ${opacity};
transition: all 150ms ease-in;
`);
`
);
function EmptyState ({dark}) {
function EmptyState({dark}) {
const [opacity, setOpacity] = React.useState(0);
const timer = React.useRef();
React.useEffect(() => {
@ -102,8 +108,16 @@ function EmptyState ({dark}) {
return (
<Container opacity={opacity}>
<Blob1 css={css`opacity: ${dark ? 0.15 : 1};`} />
<Blob2 css={css`opacity: ${dark ? 0.15 : 1};`} />
<Blob1
css={css`
opacity: ${dark ? 0.15 : 1};
`}
/>
<Blob2
css={css`
opacity: ${dark ? 0.15 : 1};
`}
/>
<Header dark={dark}>
{"🌱 You're all caught up"}
<Byline dark={dark}>

View File

@ -17,7 +17,11 @@ export const withTheme = C => p => (
</ThemeContext.Consumer>
);
const enhance = compose(withTheme, withTooltip, withOptimizedTouchEvents);
const enhance = compose(
withTheme,
withTooltip,
withOptimizedTouchEvents
);
export const ThemeColor = darkMode => (darkMode ? '#E91356' : '#27B768');
export const WHITE = 'rgb(255, 254, 252)';
@ -860,7 +864,7 @@ export function ProfileSection({dark, user, onLogout}) {
font-size: 20px;
`}
className="fas fa-cog"
></i>
/>
)}
<ProfileName>{user && user.name ? user.name : 'Settings'}</ProfileName>
<i
@ -868,7 +872,7 @@ export function ProfileSection({dark, user, onLogout}) {
css={css`
transform: ${menuShow ? 'rotate(180deg)' : 'rotate(0deg)'};
`}
></i>
/>
</ProfileContainer>
<InteractionMenu
show={menuShow}
@ -997,8 +1001,8 @@ export const IconLink = enhance(
? DarkTheme.Gray
: '#bfc5d1'
: p.dark
? WHITE
: 'inherit'
? WHITE
: 'inherit'
};
}
&:before {

View File

@ -124,7 +124,7 @@ export function getPRIssueIcon({type, reasons, dark, pinned}) {
color: ${PinnedColor};
font-size: 18px;
`}
></i>
/>
</NotificationIconWrapper>
);
}
@ -143,7 +143,7 @@ export function getPRIssueIcon({type, reasons, dark, pinned}) {
color: ${ThemeColor(dark)};
font-size: 18px;
`}
></i>
/>
</NotificationIconWrapper>
);
case 'Issue':
@ -159,7 +159,7 @@ export function getPRIssueIcon({type, reasons, dark, pinned}) {
color: ${ThemeColor(dark)};
font-size: 18px;
`}
></i>
/>
</NotificationIconWrapper>
);
default:
@ -213,7 +213,7 @@ export function iconsOfBadges(badges) {
css={css`
color: ${BadgeColors.RED};
`}
></i>
/>
);
case Badges.COMMENTS:
return (
@ -222,7 +222,7 @@ export function iconsOfBadges(badges) {
css={css`
color: ${BadgeColors.BLUE};
`}
></i>
/>
);
case Badges.OLD:
return (
@ -231,7 +231,7 @@ export function iconsOfBadges(badges) {
css={css`
color: ${BadgeColors.YELLOW};
`}
></i>
/>
);
default:
return null;

View File

@ -44,20 +44,18 @@ const Container = styled('div')`
${forSmallScreens(`
padding-left: 2.5rem;
padding-right: 2.5rem;
`)}
${forMobile(`
`)} ${forMobile(`
margin-bottom: 2.5rem;
`)}
`)};
`;
const LightContainer = styled(Container)`
${forSmallScreens(`
padding-left: 0;
padding-right: 0;
`)}
${forMobile(`
`)} ${forMobile(`
margin-bottom: 2.5rem;
`)}
`)};
`;
const FlexItem = styled('div')`
@ -146,7 +144,7 @@ const HeroTitle = styled('h1')`
font-size: 46px;
line-height: 54px;
margin: 0 auto 6px;
`)}
`)};
`;
const HeroSubtitle = styled('h1')`
@ -163,7 +161,7 @@ const HeroSubtitle = styled('h1')`
margin: 0 auto 32px;
font-size: 20px;
line-height: 24px;
`)}
`)};
`;
const Title = styled('h1')`
@ -209,7 +207,7 @@ const HeroLeft = styled(FlexItem)`
margin: 0 auto;
width: 100%;
text-align: center;
`)}
`)};
`;
const HeroRight = styled(FlexItem)`
@ -218,7 +216,7 @@ const HeroRight = styled(FlexItem)`
${forMobile(`
margin: 0 auto;
width: 100%;
`)}
`)};
`;
const PricingContainer = styled('div')`
@ -233,9 +231,7 @@ const PricingContainer = styled('div')`
text-align: left;
${forMobile(`
width: 100%;
`)}
h4 {
`)} h4 {
margin: -2px 0 0;
font-size: 20px;
color: #26b768;
@ -285,9 +281,7 @@ const FloatingLinkBox = styled('div')`
margin: 0 24px 0 0;
${forMobile(`
margin: 0 0 24px 0;
`)}
h4 {
`)} h4 {
margin: 0;
color: #47494b;
font-size: 18px;
@ -326,8 +320,7 @@ const FAQItem = styled('div')`
flex: 1 0 34%;
${forMobile(`
flex: 1 0 51%;
`)}
flex-direction: column;
`)} flex-direction: column;
display: flex;
align-items: left;
justify-content: center;
@ -368,7 +361,7 @@ export default function Scene(props) {
css={css`
${forMobile(`
flex-direction: column;
`)}
`)};
`}
>
<HeroLeft>
@ -399,7 +392,7 @@ export default function Scene(props) {
<Container
css={css`
${forMobile(`flex-direction: column;`)}
${forMobile(`flex-direction: column;`)};
`}
>
<FloatingLinkBox>

View File

@ -1,8 +1,8 @@
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 {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 {
@ -10,17 +10,14 @@ class PricingPage extends React.Component {
// Remove cookie and invalidate token on client.
this.props.cookiesApi.removeCookie(OAUTH_TOKEN_COOKIE);
this.props.authApi.invalidateToken();
}
};
render () {
render() {
return (
<Scene
loggedIn={!!this.props.authApi.token}
onLogout={this.onLogout}
/>
<Scene loggedIn={!!this.props.authApi.token} onLogout={this.onLogout} />
);
}
};
}
const enhance = compose(
withAuthProvider,

View File

@ -49,10 +49,9 @@ const Container = styled('div')`
${forSmallScreens(`
padding-left: 2.0rem;
padding-right: 2.0rem;
`)}
${forMobile(`
`)} ${forMobile(`
margin-bottom: 2.5rem;
`)}
`)};
`;
const FlexItem = styled('div')`
@ -83,9 +82,7 @@ const ButtonLink = styled('a')`
transition: all 0.15s ease-in-out;
${forMobile(`
padding: 0.125rem 0.25rem;
`)}
&:hover {
`)} &:hover {
background-color: #f4f4f4;
border-color: #f4f4f4;
}
@ -120,9 +117,7 @@ const Button = styled(RouterLink)`
transition: all 0.15s ease-in-out;
${forMobile(`
padding: 0.125rem 0.25rem;
`)}
&:hover {
`)} &:hover {
background-color: #f4f4f4;
border-color: #f4f4f4;
}
@ -163,7 +158,7 @@ const LogoTitle = styled('span')`
${forMobile(`
display: none;
`)}
`)};
`;
const LogoSection = () => (
@ -176,8 +171,7 @@ const LogoSection = () => (
width: 125px;
${forMobile(`
width: 36px;
`)}
div {
`)} div {
display: inline-block;
margin-right: 8px;
}
@ -277,7 +271,7 @@ export function BasicPageWrapper({loggedIn, onLogout, children}) {
border-radius: 100%;
${forMobile(`
right: -1px;
`)}
`)};
}
`}
>

View File

@ -1,35 +1,36 @@
import React from 'react';
import { withCookiesProvider } from './Cookies';
import { OAUTH_TOKEN_COOKIE } from '../constants/cookies';
import {withCookiesProvider} from './Cookies';
import {OAUTH_TOKEN_COOKIE} from '../constants/cookies';
const {Provider, Consumer} = React.createContext();
class AuthProvider extends React.Component {
state = {
token: (
token:
this.props.cookiesApi.getCookie(OAUTH_TOKEN_COOKIE) ||
(process.localEnv.NODE_ENV === 'local'
? process.localEnv.OAUTH_TOKEN
: undefined)
)
}
};
setToken = token => {
this.props.cookiesApi.setCookie(OAUTH_TOKEN_COOKIE, token);
this.setState({ token });
}
this.setState({token});
};
invalidateToken = () => {
this.setState({ token: null });
}
this.setState({token: null});
};
render () {
render() {
return (
<Provider value={{
token: this.state.token,
setToken: this.setToken,
invalidateToken: this.invalidateToken
}}>
<Provider
value={{
token: this.state.token,
setToken: this.setToken,
invalidateToken: this.invalidateToken
}}
>
{this.props.children}
</Provider>
);

View File

@ -4,42 +4,46 @@ import moment from 'moment';
class CookiesProvider extends React.Component {
state = {
cookies: {}
}
};
componentWillMount () {
componentWillMount() {
this.hydrateCookies();
}
mapifyCookies = () => {
const cookiesPairs = document.cookie.split(';').map(cookie => cookie.trim());
const cookiesPairs = document.cookie
.split(';')
.map(cookie => cookie.trim());
const cookies = cookiesPairs.reduce((map, cookiePair) => {
const [key, value] = cookiePair.split('=');
map[key] = value;
return map;
}, {});
return cookies;
}
};
hydrateCookies = () => {
const cookies = this.mapifyCookies();
this.setState({ cookies });
}
this.setState({cookies});
};
setCookie = (name, value) => {
document.cookie = `${name}=${value}`;
this.hydrateCookies()
}
this.hydrateCookies();
};
getCookie = name => {
return this.state.cookies[name];
}
};
removeCookie = name => {
document.cookie = `${name}=''; expires=${moment().subtract(1, 'day').toString()}`;
document.cookie = `${name}=''; expires=${moment()
.subtract(1, 'day')
.toString()}`;
this.hydrateCookies();
}
};
render () {
render() {
return this.props.children({
...this.state,
setCookie: this.setCookie,
@ -55,7 +59,4 @@ const withCookiesProvider = WrappedComponent => props => (
</CookiesProvider>
);
export {
CookiesProvider,
withCookiesProvider
};
export {CookiesProvider, withCookiesProvider};

View File

@ -7,14 +7,14 @@ import {Status} from '../constants/status';
const BASE_GITHUB_API_URL = 'https://api.github.com';
const PER_PAGE = 50;
function transformUrlFromResponse (url) {
function transformUrlFromResponse(url) {
return url
.replace('api.github.com', 'github.com')
.replace('/repos/', '/')
.replace('/pulls/', '/pull/');
}
function processHeadersAndBodyJson (response) {
function processHeadersAndBodyJson(response) {
const entries = response.headers.entries();
const headers = {};
for (let [name, value] of entries) {
@ -25,7 +25,7 @@ function processHeadersAndBodyJson (response) {
const links = {};
if (rawLinks) {
rawLinks.split(',').forEach((p) => {
rawLinks.split(',').forEach(p => {
const section = p.split(';');
if (section.length !== 2) {
throw new Error("section could not be split on ';'");
@ -34,7 +34,7 @@ function processHeadersAndBodyJson (response) {
const page = section[0].match(/page=(\d)/)[1];
const name = section[1].replace(/rel="(.*)"/, '$1').trim();
links[name] = {url, page};
})
});
}
// links.next.page
headers['link'] = links;
@ -61,7 +61,7 @@ function processHeadersAndBodyJson (response) {
}
class NotificationsProvider extends React.Component {
constructor (props) {
constructor(props) {
super(props);
this.last_modified = null;
@ -73,18 +73,19 @@ class NotificationsProvider extends React.Component {
error: null,
newChanges: null,
notificationsPermission:
this.props.getUserItem('notificationsPermission') ||
'default',
}
this.props.getUserItem('notificationsPermission') || 'default'
};
shouldComponentUpdate (nextProps, nextState) {
shouldComponentUpdate(nextProps, nextState) {
// Don't try to rerender if we're just setting the new changes.
if (this.state.newChanges !== nextState.newChanges) {
return false;
}
// Update if our state changes.
if ((this.state.loading !== nextState.loading) ||
(this.state.error !== nextState.error)) {
if (
this.state.loading !== nextState.loading ||
this.state.error !== nextState.error
) {
return true;
}
// Update if the token changes at all (sign in & sign out).
@ -93,9 +94,7 @@ class NotificationsProvider extends React.Component {
}
// Only update if our notifications prop changes.
// All other props "changing" should NOT trigger a rerender.
return (
this.props.notifications !== nextProps.notifications
);
return this.props.notifications !== nextProps.notifications;
}
// The web notificaitons API doesn't let users revoke notifications permission
@ -106,12 +105,12 @@ class NotificationsProvider extends React.Component {
this.setState({notificationsPermission: permission});
this.props.setUserItem('notificationsPermission', permission);
this.forceUpdate();
}
};
request = (url, method = 'GET') => {
const headers = {
'Authorization': `token ${this.props.token}`,
'Content-Type': 'application/json',
Authorization: `token ${this.props.token}`,
'Content-Type': 'application/json'
};
// @TODO probably add timestamp
@ -123,26 +122,27 @@ class NotificationsProvider extends React.Component {
return fetch(url, {
method,
headers
}).then(processHeadersAndBodyJson)
})
.then(processHeadersAndBodyJson)
.then(({json}) => {
console.info(`Response from %c${url}`, 'font-weight: bold;')
console.info(json)
console.info('')
console.info(`Response from %c${url}`, 'font-weight: bold;');
console.info(json);
console.info('');
this.props.setUserItem(url, json);
return json;
})
.catch(({status, text}) => {
console.info(`Response from %c${url}`, 'font-weight: bold;')
console.info(`${status}: ${text}`)
console.info('')
console.info(`Response from %c${url}`, 'font-weight: bold;');
console.info(`${status}: ${text}`);
console.info('');
this.props.setUserItem(url, null);
});
}
};
requestUser = () => {
const headers = {
'Authorization': `token ${this.props.token}`,
'Content-Type': 'application/json',
Authorization: `token ${this.props.token}`,
'Content-Type': 'application/json'
};
// @TODO probably add timestamp
@ -154,12 +154,13 @@ class NotificationsProvider extends React.Component {
return fetch(`${BASE_GITHUB_API_URL}/user`, {
method: 'GET',
headers: headers
}).then(processHeadersAndBodyJson)
})
.then(processHeadersAndBodyJson)
.then(({json}) => {
this.props.setUserItem('user-model', json);
return json;
});
}
};
requestPage = (page = 1, optimizePolling = true) => {
// Fetch all notifications from a month ago, including ones that have been read.
@ -169,21 +170,28 @@ class NotificationsProvider extends React.Component {
// the middle of the things you're working on and only having part of the story.
//
// 1 month is pretty arbitrary, we can raise this if we want.
const since = moment().subtract(1, 'month').toISOString().split('.')[0] + 'Z';
const since =
moment()
.subtract(1, 'month')
.toISOString()
.split('.')[0] + 'Z';
const headers = {
'Authorization': `token ${this.props.token}`,
'Content-Type': 'application/json',
Authorization: `token ${this.props.token}`,
'Content-Type': 'application/json'
};
if (optimizePolling && this.last_modified) {
headers['If-Modified-Since'] = this.last_modified;
}
return fetch(`${BASE_GITHUB_API_URL}/notifications?page=${page}&per_page=${PER_PAGE}&since=${since}&all=true`, {
method: 'GET',
headers: headers
})
return fetch(
`${BASE_GITHUB_API_URL}/notifications?page=${page}&per_page=${PER_PAGE}&since=${since}&all=true`,
{
method: 'GET',
headers: headers
}
)
.then(processHeadersAndBodyJson)
.then(({headers, json}) => {
// If there were updates, make sure we get the newest last-modified.
@ -203,7 +211,7 @@ class NotificationsProvider extends React.Component {
}
return this.processNotificationsChunk(nextPage, json);
});
}
};
requestFetchNotifications = (page = 1, optimizePolling = true) => {
if (this.state.syncing) {
@ -212,9 +220,10 @@ class NotificationsProvider extends React.Component {
}
this.setState({syncing: true});
return this.requestPage(page, optimizePolling)
.finally(() => this.setState({syncing: false}));
}
return this.requestPage(page, optimizePolling).finally(() =>
this.setState({syncing: false})
);
};
fetchNotifications = (page = 1, optimizePolling = true) => {
if (!this.props.token) {
@ -227,12 +236,12 @@ class NotificationsProvider extends React.Component {
return;
}
this.setState({ loading: true });
this.setState({loading: true});
return this.requestFetchNotifications(page, optimizePolling)
.then(() => this.setState({error: null}))
.catch(error =>this.setState({error}))
.finally(() => this.setState({ loading: false }));
}
.catch(error => this.setState({error}))
.finally(() => this.setState({loading: false}));
};
processNotificationsChunk = (nextPage, notificationsChunk) => {
return new Promise((resolve, reject) => {
@ -278,12 +287,12 @@ class NotificationsProvider extends React.Component {
return resolve();
}
});
}
};
requestMarkAsRead = thread_id => {
const headers = {
'Authorization': `token ${this.props.token}`,
'Content-Type': 'application/json',
Authorization: `token ${this.props.token}`,
'Content-Type': 'application/json'
};
return fetch(`${BASE_GITHUB_API_URL}/notifications/threads/${thread_id}`, {
@ -291,20 +300,18 @@ class NotificationsProvider extends React.Component {
headers: headers
})
.then(response => {
return response.status === 205
? Promise.resolve()
: Promise.reject();
return response.status === 205 ? Promise.resolve() : Promise.reject();
})
.then(() => {
this.props.removeItemFromStorage(thread_id);
this.props.refreshNotifications();
return Promise.resolve();
});
}
};
markAsRead = thread_id => {
if (!this.props.token) {
console.error('Unauthenitcated, aborting request.')
console.error('Unauthenitcated, aborting request.');
return false;
}
@ -313,12 +320,12 @@ class NotificationsProvider extends React.Component {
return;
}
this.setState({ loading: true });
this.setState({loading: true});
return this.requestMarkAsRead(thread_id)
.then(() => this.setState({error: null}))
.catch(error => this.setState({error}))
.finally(() => this.setState({ loading: false }));
}
.finally(() => this.setState({loading: false}));
};
requestClearCache = () => {
return new Promise((resolve, reject) => {
@ -327,15 +334,15 @@ class NotificationsProvider extends React.Component {
this.last_modified = null;
return resolve();
});
}
};
clearCache = () => {
this.setState({ loading: true });
this.setState({loading: true});
return this.requestClearCache()
.then(() => this.setState({error: null}))
.catch(error => this.setState({error}))
.finally(() => this.setState({ loading: false }));
}
.finally(() => this.setState({loading: false}));
};
requestStageThread = thread_id => {
return new Promise((resolve, reject) => {
@ -350,10 +357,12 @@ class NotificationsProvider extends React.Component {
this.props.refreshNotifications();
return resolve();
} else {
throw new Error(`Attempted to stage thread ${thread_id} that wasn't found in the cache.`);
throw new Error(
`Attempted to stage thread ${thread_id} that wasn't found in the cache.`
);
}
});
}
};
requestPinThread = thread_id => {
return new Promise((resolve, reject) => {
@ -368,10 +377,12 @@ class NotificationsProvider extends React.Component {
this.props.refreshNotifications();
return resolve();
} else {
throw new Error(`Attempted to stage thread ${thread_id} that wasn't found in the cache.`);
throw new Error(
`Attempted to stage thread ${thread_id} that wasn't found in the cache.`
);
}
});
}
};
requestReadPinThread = thread_id => {
return new Promise((resolve, reject) => {
@ -386,10 +397,12 @@ class NotificationsProvider extends React.Component {
this.props.refreshNotifications();
return resolve();
} else {
throw new Error(`Attempted to stage thread ${thread_id} that wasn't found in the cache.`);
throw new Error(
`Attempted to stage thread ${thread_id} that wasn't found in the cache.`
);
}
});
}
};
requestStageAll = () => {
return new Promise((resolve, reject) => {
@ -410,7 +423,7 @@ class NotificationsProvider extends React.Component {
this.props.refreshNotifications();
return resolve();
});
}
};
requestRestoreThread = thread_id => {
return new Promise((resolve, reject) => {
@ -425,54 +438,56 @@ class NotificationsProvider extends React.Component {
this.props.refreshNotifications();
return resolve();
} else {
throw new Error(`Attempted to restore thread ${thread_id} that wasn't found in the cache.`);
throw new Error(
`Attempted to restore thread ${thread_id} that wasn't found in the cache.`
);
}
});
}
};
markAllAsStaged = () => {
this.setState({ loading: true });
this.setState({loading: true});
return this.requestStageAll()
.then(() => this.setState({error: null}))
.catch(error => this.setState({error}))
.finally(() => this.setState({ loading: false }));
}
.finally(() => this.setState({loading: false}));
};
stageThread = thread_id => {
return this.requestStageThread(thread_id)
.then(() => this.setState({error: null}))
.catch(error => this.setState({error}));
}
};
pinThread = thread_id => {
return this.requestPinThread(thread_id)
.then(() => this.setState({error: null}))
.catch(error => this.setState({error}));
}
};
readPinThread = thread_id => {
return this.requestReadPinThread(thread_id)
.then(() => this.setState({error: null}))
.catch(error => this.setState({error}));
}
};
restoreThread = thread_id => {
return this.requestRestoreThread(thread_id)
.then(() => this.setState({error: null}))
.catch(error => this.setState({error}));
}
};
updateNotification = (n, cachedNotification = null) => {
const prevReason = cachedNotification ? cachedNotification.reasons : null;
const isPinned = cachedNotification ? (
cachedNotification.status === Status.Pinned ||
cachedNotification.status === Status.PinnedRead
) : false;
const isPinned = cachedNotification
? cachedNotification.status === Status.Pinned ||
cachedNotification.status === Status.PinnedRead
: false;
let reasons = [];
const newReason = {
reason: n.reason,
time: n.updated_at
}
};
if (prevReason) {
reasons = prevReason.concat(newReason);
@ -485,7 +500,9 @@ class NotificationsProvider extends React.Component {
: null;
const url = commentNumber
? transformUrlFromResponse(n.subject.url) + '#issuecomment-' + commentNumber
? transformUrlFromResponse(n.subject.url) +
'#issuecomment-' +
commentNumber
: transformUrlFromResponse(n.subject.url);
let nextStatus = null;
@ -522,9 +539,9 @@ class NotificationsProvider extends React.Component {
};
this.props.setItemInStorage(n.id, value);
return value;
}
};
render () {
render() {
return this.props.children({
...this.state,
request: this.request,
@ -539,14 +556,14 @@ class NotificationsProvider extends React.Component {
restoreThread: this.restoreThread,
pinThread: this.pinThread,
readPinThread: this.readPinThread,
setNotificationsPermission: this.setNotificationsPermission,
setNotificationsPermission: this.setNotificationsPermission
});
}
}
const withNotificationsProvider = WrappedComponent => props => (
<AuthConsumer>
{({ token }) => (
{({token}) => (
<StorageProvider>
{({
refreshNotifications,
@ -569,8 +586,11 @@ const withNotificationsProvider = WrappedComponent => props => (
removeItemFromStorage={removeItem}
token={token}
>
{(notificationsApi) => (
<WrappedComponent {...props} notificationsApi={notificationsApi} />
{notificationsApi => (
<WrappedComponent
{...props}
notificationsApi={notificationsApi}
/>
)}
</NotificationsProvider>
)}
@ -579,7 +599,4 @@ const withNotificationsProvider = WrappedComponent => props => (
</AuthConsumer>
);
export {
NotificationsProvider,
withNotificationsProvider
};
export {NotificationsProvider, withNotificationsProvider};

View File

@ -17,7 +17,7 @@ export const TriageLimit = {
};
class StorageProvider extends React.Component {
constructor (props) {
constructor(props) {
super(props);
this.originalTitle = document.title;
@ -28,13 +28,13 @@ class StorageProvider extends React.Component {
loading: false,
error: null,
notifications: []
}
};
componentWillMount () {
componentWillMount() {
this.refreshNotifications();
}
componentDidMount () {
componentDidMount() {
window.onfocus = () => this.setTitle(this.originalTitle);
}
@ -42,14 +42,13 @@ class StorageProvider extends React.Component {
if (document.title.indexOf('(1)') === -1 && document.title !== title) {
document.title = title;
}
}
};
/**
* Loads up the notifications state with the cache.
*/
refreshNotifications = () => {
const notifications = Object
.keys(window.localStorage)
const notifications = Object.keys(window.localStorage)
.reduce((acc, key) => {
if (key.indexOf(LOCAL_STORAGE_PREFIX) > -1) {
const cached_n = JSON.parse(window.localStorage.getItem(key));
@ -62,8 +61,7 @@ class StorageProvider extends React.Component {
// a notification, however we should fallback to `updated_at` in case
// there is a thread that doesn't have this set yet.
const lastUpdated = moment(
notification.status_last_changed ||
notification.updated_at
notification.status_last_changed || notification.updated_at
);
const daysOld = moment().diff(lastUpdated, 'days');
@ -105,12 +103,12 @@ class StorageProvider extends React.Component {
return false;
});
this.setState({ notifications });
this.setState({notifications});
// Faux notifications for sample screenshots.
// const mockNotifications = createMockNotifications(20);
// this.setState({ notifications: mockNotifications });
}
};
/**
* Stats are broken up since they are fetched and set often, we want to avoid
@ -134,18 +132,24 @@ class StorageProvider extends React.Component {
// Range reflects `[start, end)`
for (let m = startTime.clone(); m.isBefore(endTime); m.add(1, 'day')) {
const key = m.format('YYYY-MM-DD');
const value = window.localStorage.getItem(`${LOCAL_STORAGE_STATISTIC_PREFIX}${key}-${stat}`);
const value = window.localStorage.getItem(
`${LOCAL_STORAGE_STATISTIC_PREFIX}${key}-${stat}`
);
if (value) {
response.push(value);
} else {
// If the date is in the past or present, give it a value of 0. Otherwise, null.
const fauxValue = m.clone().startOf('day').isSameOrBefore(
currentTime.clone().startOf('day')) ? 0 : null;
const fauxValue = m
.clone()
.startOf('day')
.isSameOrBefore(currentTime.clone().startOf('day'))
? 0
: null;
response.push(fauxValue);
}
}
return response;
}
};
/**
* Since our stats right now are just numbers, we can assume "setting" will always
@ -154,13 +158,21 @@ class StorageProvider extends React.Component {
*/
incrStat = (stat, additionalPrefix = moment().format('YYYY-MM-DD')) => {
const key = additionalPrefix ? `${additionalPrefix}-` : '';
const oldValue = window.localStorage.getItem(`${LOCAL_STORAGE_STATISTIC_PREFIX}${key}${stat}`);
const oldValue = window.localStorage.getItem(
`${LOCAL_STORAGE_STATISTIC_PREFIX}${key}${stat}`
);
if (oldValue !== null) {
window.localStorage.setItem(`${LOCAL_STORAGE_STATISTIC_PREFIX}${key}${stat}`, parseInt(oldValue, 10) + 1);
window.localStorage.setItem(
`${LOCAL_STORAGE_STATISTIC_PREFIX}${key}${stat}`,
parseInt(oldValue, 10) + 1
);
} else {
window.localStorage.setItem(`${LOCAL_STORAGE_STATISTIC_PREFIX}${key}${stat}`, 1);
window.localStorage.setItem(
`${LOCAL_STORAGE_STATISTIC_PREFIX}${key}${stat}`,
1
);
}
}
};
getAllRepoStagedCounts = () => {
return Object.keys(window.localStorage)
@ -172,42 +184,55 @@ class StorageProvider extends React.Component {
}
// Janky but will work.
const repo = key.split('__REPO__-').pop().split('-stagedCount')[0];
const repo = key
.split('__REPO__-')
.pop()
.split('-stagedCount')[0];
repos[repo] = value;
return repos;
}, {});
}
};
// val value : Object
setItem = (id, value) => {
window.localStorage.setItem(`${LOCAL_STORAGE_PREFIX}${id}`, JSON.stringify(value));
}
window.localStorage.setItem(
`${LOCAL_STORAGE_PREFIX}${id}`,
JSON.stringify(value)
);
};
getItem = id => {
try {
return JSON.parse(window.localStorage.getItem(`${LOCAL_STORAGE_PREFIX}${id}`));
return JSON.parse(
window.localStorage.getItem(`${LOCAL_STORAGE_PREFIX}${id}`)
);
} catch (e) {
return window.localStorage.getItem(`${LOCAL_STORAGE_PREFIX}${id}`);
}
}
};
getUserItem = id => {
try {
return JSON.parse(window.localStorage.getItem(`${LOCAL_STORAGE_USER_PREFIX}${id}`));
return JSON.parse(
window.localStorage.getItem(`${LOCAL_STORAGE_USER_PREFIX}${id}`)
);
} catch (e) {
return window.localStorage.getItem(`${LOCAL_STORAGE_USER_PREFIX}${id}`);
}
}
};
setUserItem = (id, value) => {
window.localStorage.setItem(`${LOCAL_STORAGE_USER_PREFIX}${id}`, JSON.stringify(value));
}
window.localStorage.setItem(
`${LOCAL_STORAGE_USER_PREFIX}${id}`,
JSON.stringify(value)
);
};
// Actually does the work of deleting the item from the cache.
deleteItem = id => {
window.localStorage.removeItem(`${LOCAL_STORAGE_PREFIX}${id}`);
}
};
removeItem = id => {
// We never really want to purge anything from the cache if we can help it,
@ -222,11 +247,10 @@ class StorageProvider extends React.Component {
status: Status.CLOSED
};
this.setItem(id, closed_cached_n);
}
};
clearArchivedCache = () => {
Object
.keys(window.localStorage)
Object.keys(window.localStorage)
.reduce((acc, key) => {
if (key.indexOf(LOCAL_STORAGE_PREFIX) > -1) {
const cached_n = JSON.parse(window.localStorage.getItem(key));
@ -236,13 +260,13 @@ class StorageProvider extends React.Component {
}, [])
.filter(notification => notification.status === Status.Archived)
.forEach(notification => this.deleteItem(notification.id));
}
};
clearCache = () => {
window.localStorage.clear();
}
};
render () {
render() {
return this.props.children({
...this.state,
setItem: this.setItem,
@ -255,20 +279,15 @@ class StorageProvider extends React.Component {
refreshNotifications: this.refreshNotifications,
getStat: this.getStat,
getAllRepoStagedCounts: this.getAllRepoStagedCounts,
incrStat: this.incrStat,
incrStat: this.incrStat
});
}
}
const withStorageProvider = WrappedComponent => props => (
<StorageProvider>
{(storageApi) => (
<WrappedComponent {...props} storageApi={storageApi} />
)}
{storageApi => <WrappedComponent {...props} storageApi={storageApi} />}
</StorageProvider>
);
export {
StorageProvider,
withStorageProvider
};
export {StorageProvider, withStorageProvider};

View File

@ -1,4 +1,3 @@
const facts = [
'Movie trailers were originally shown after the movie, which is why they were called “trailers”.',
'The top six foods that make your fart are beans, corn, bell peppers, cauliflower, cabbage and milk!',
@ -40,7 +39,7 @@ const facts = [
'12+1 = 11+2, and "twelve plus one" is an anagram of "eleven plus two."'
];
export function getFact () {
export function getFact() {
// DoUbLe TiLdE iS mOrE eLeGaNt ThAn MaTh.FlOoR
return facts[~~(Math.random() * facts.length)];
}

View File

@ -2,9 +2,9 @@ import moment from 'moment';
import {Reasons} from '../constants/reasons';
import {Status} from '../constants/status';
function* createCounterGenerator () {
function* createCounterGenerator() {
let i = 0;
while(true) yield i++
while (true) yield i++;
}
const uidGen = createCounterGenerator();
@ -19,7 +19,7 @@ const getMockReasons = (n, ra) => {
}));
};
function getMockName (index) {
function getMockName(index) {
const names = [
'[MPQ-43] Feature - Improve native events performance',
'Chore - Remove redundent constants file',
@ -27,29 +27,36 @@ function getMockName (index) {
'Feature - Update Storage bindings to be fastpipe compatible',
'[RFC] Standard Library Interfaces',
'[Belt] Change reduceReverse to reduceRight',
'[Experiment][DoNotmerge] Represent OCaml records as JS objects at runtime',
'[Experiment][DoNotmerge] Represent OCaml records as JS objects at runtime'
];
return names[index % names.length];
}
const getMockNotification = (ra, rb, rc) => ({
id: uidGen.next().value,
updated_at: moment().subtract(ra * 500, 'minutes').utc().format(),
updated_at: moment()
.subtract(ra * 500, 'minutes')
.utc()
.format(),
isAuthor: ra < 0.2,
status: ra < 0.4 ? Status.Unread : rb < 0.6 ? Status.Read : Status.Archived,
reasons: getMockReasons(Math.ceil(rb * 10), ra),
type: ['PullRequest', 'Issue'][rc > .7 ? 1 : 0],
type: ['PullRequest', 'Issue'][rc > 0.7 ? 1 : 0],
name: getMockName(indexGen.next().value),
url: 'https://github.com/test/repo/pull',
repository: ['BuckleScript/bucklescript', 'nickzuber/meteorite'][Math.floor(rb * 2)],
repository: ['BuckleScript/bucklescript', 'nickzuber/meteorite'][
Math.floor(rb * 2)
],
number: Math.ceil(rc * 1000),
repositoryUrl: 'https://github.com/test/repo',
repositoryUrl: 'https://github.com/test/repo'
});
export function createMockNotifications (n = 50) {
export function createMockNotifications(n = 50) {
const mockNotifications = new Array(n);
for (let i = 0; i < mockNotifications.length; i++) {
let a = Math.random(), b = Math.random(), c = Math.random();
let a = Math.random(),
b = Math.random(),
c = Math.random();
mockNotifications[i] = getMockNotification(a, b, c);
}
return mockNotifications;

View File

@ -1,4 +1,4 @@
export function inlineSvg (svg) {
export function inlineSvg(svg) {
const b64 = btoa(svg);
return `"data:image/svg+xml;base64,${b64}"`;
}