Work on mobile, more home page progress

This commit is contained in:
Nicholas Zuber 2019-04-20 17:50:29 -04:00
parent 02024612ca
commit 6ef9746d85
17 changed files with 320 additions and 53 deletions

View File

@ -9,7 +9,8 @@
}],
"routes": [
{"src": "^/static/(.*)", "dest": "/static/$1"},
{"src": "^/favicon.ico", "dest": "/favicon.ico"},
{"src": "^/favicon.ico", "dest": "/iconCircle.png"},
{"src": "^/iconCircle.png", "dest": "/iconCircle.png"},
{"src": "^/manifest.json", "dest": "/manifest.json"},
{"src": "^/(.*)", "dest": "/index.html"}
],

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<link rel="shortcut icon" href="%PUBLIC_URL%/iconCircle.png">
<!-- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> -->
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
<meta name="theme-color" content="#24292e">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB

View File

@ -2,15 +2,20 @@
import React from 'react';
import styled from '@emotion/styled';
import {css, jsx, keyframes} from '@emotion/core';
import useComponentSize from '@rehooks/component-size'
import {css, jsx} from '@emotion/core';
import {Link as RouterLink} from '@reach/router';
import {routes} from '../../constants';
import Curve from '../../components/Curve';
import Icon from '../../components/Icon';
import Logo from '../../components/Logo';
import demoPng from '../../images/demo.png';
import rowExample from '../../images/row.png';
import headerPng from '../../images/safari-header.png';
import regularScreenshotPng from '../../images/traditional-screenshot.png';
import IPhoneXMockupPng from '../../images/screenshots/iphone-x-mockup.png';
import IPhoneScreenshotPng from '../../images/screenshots/iphone-x.png';
import RobinLogo from '../../images/logos/robin-logo.png';
import RemoteHQLogo from '../../images/logos/remote-hq-logo.png';
import ReactNativeLogo from '../../images/logos/react-logo.png';
import '../../styles/gradient.css';
import '../../styles/font.css';
@ -30,6 +35,14 @@ function color (rand) {
'#4C84FF',
'#e6d435',
];
const newer_c = [
'#B424E6',
'#1ACA6B',
'#E62465',
'#FFCD4C',
'#4C84FF',
'#E9519A',
];
return c[~~(c.length * rand)];
}
@ -587,9 +600,11 @@ const PageContainer = styled('div')`
overflow: hidden;
background: #fcf8f3;
position: relative;
min-height: calc(100vh - 84px);
height: 100%;
width: 100%;
display: block;
overflow: hidden;
`;
const FixedContainer = styled('div')`
@ -601,6 +616,7 @@ const FixedContainer = styled('div')`
`;
const Container = styled('div')`
position: relative;
background: rgb(252, 248, 243);
width: 960px;
margin: 0 auto;
@ -644,12 +660,63 @@ const Header = styled('h1')`
font-size: 72px;
line-height: 78px;
margin: 0 auto 12px;
font-weight: 800;
font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif;
font-weight: 500;
`;
const ItemHeader = styled(Header)`
position: relative;
text-align: left;
width: 680px;
margin: 0;
z-index: 2;
font-size: 62px;
line-height: 64px;
margin: 0 0 12px;
font-family: medium-marketing-display-font,Georgia,Cambria,Times New Roman,Times,serif;
font-weight: 500;
`;
const HorizontalListItem = styled('div')`
flex: 1;
border-right: ${props => props.last ? '0px' : '1px'} solid rgb(206, 206, 206);
padding: 0 32px;
`;
const Quote = styled('p')`
margin: 0;
font-size: 15px;
font-weight: 400;
&:before {
content: open-quote;
}
&:after {
content: close-quote;
}
`;
const CompanyPerson = styled('div')`
transform: scale(0.95);
display: flex;
padding: 8px 0;
margin: 0;
img {
display: block;
height: 30px;
width: 30px;
border-radius: 2px;
}
span {
display: inline-block;
padding: 0 16px;
font-size: 14px;
line-height: 18px;
color: #37352f80;
}
`;
function getConfetti (seed) {
const stroke = color(seed);
const rotation = ~~(seed * 180);
@ -755,6 +822,7 @@ function ConfettiSection () {
}
const SubHeader = styled(Header)`
hyphens: auto;
font-size: 22px;
line-height: 26px;
font-weight: 600;
@ -766,12 +834,37 @@ const SubHeader = styled(Header)`
font-weight: 500;
`;
const DemoScreenshot = styled('img')`
max-width: 960px;
const DemoScreenshotHeader = styled('img')`
background: #f7f6f3;
width: 960px;
display: block;
box-shadow: rgba(0, 0, 0, 0.15) 0px 10px 20px, rgb(245, 245, 245) 0px -1px 0px;
border-radius: 4px;
z-index: 2;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
z-index: 3;
`;
const DemoScreenshot = styled(DemoScreenshotHeader)`
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
`;
const IPhoneScreenshotContainer = styled('img')`
position: absolute;
bottom: -32px;
right: -32px;
width: 214px;
display: block;
background: none;
z-index: 5;
`;
const IPhoneScreenshot = styled(IPhoneScreenshotContainer)`
z-index: 4;
transform: scale(0.88);
bottom: -48px;
`;
const SmallText = styled('p')`
@ -786,7 +879,7 @@ const SmallText = styled('p')`
const SmallLink = styled('a')`
margin: 0;
font-size: 12px;
font-weight: 500;
font-weight: 600;
display: inline-block;
color: #37352f;
z-index: 2;
@ -903,13 +996,13 @@ export default function Scene ({loggedIn, onLogout, ...props}) {
</FixedContainer>
<Container css={css`
margin: 36px auto;
margin: 32px auto;
padding: 8px;
flex-direction: column;
`}>
<div css={css`
position: relative;
margin: 0 auto 36px;
margin: 0 auto 18px;
display: flex;
justify-content: space-between;
align-items: center;
@ -1016,7 +1109,114 @@ export default function Scene ({loggedIn, onLogout, ...props}) {
</div>
</div>
</div>
<DemoScreenshot src={demoPng} />
<div css={css`
position: relative;
`}>
<DemoScreenshotHeader src={headerPng} />
<DemoScreenshot src={regularScreenshotPng} />
<IPhoneScreenshotContainer src={IPhoneXMockupPng} />
<IPhoneScreenshot src={IPhoneScreenshotPng} />
</div>
</Container>
<Container css={css`
position: relative;
margin: 88px auto 32px;
padding: 48px 16px;
align-items: center;
flex-direction: column;
border-top: 1px solid rgb(221, 221, 221);
border-bottom: 1px solid rgb(221, 221, 221);
`}>
<div css={css`
position: relative;
margin: 0 0 18px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
flex-direction: column;
`}>
<ItemHeader css={css`
margin-top: 32px;
font-size: 48px;
line-height: 50px;
text-align: center;
`}>
{'Hear what folks have to say'}
</ItemHeader>
<SubHeader css={css`
text-align: center;
margin-bottom: 32px;
`}>
{'People have been using this app for a while now and have been kind enough to share their thoughts.'}
</SubHeader>
<div css={css`
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 32px;
`}>
<HorizontalListItem>
<Quote>{`Nick is absolutely KILLING THE GAME with this app. I LOVE it!`}</Quote>
<CompanyPerson>
<img src={RemoteHQLogo} />
<span>
{'— Trevor Suarez'}<br />
{'RemoteHQ, Software Engineer'}
</span>
</CompanyPerson>
</HorizontalListItem>
<HorizontalListItem>
<Quote>{`So good! I love the importance sorting!`}</Quote>
<CompanyPerson>
<img css={css`filter: hue-rotate(222deg);`} src={ReactNativeLogo} />
<span>
{'— Mike Grabowski'}<br />
{'Software Architect and Core Contributor to React Native'}
</span>
</CompanyPerson>
</HorizontalListItem>
<HorizontalListItem last>
<Quote>{`I actually use this all the time.`}</Quote>
<CompanyPerson>
<img src={RobinLogo} />
<span>
{'— Henry Lee'}<br />
{'Robin, Frontend Software Engineer'}
</span>
</CompanyPerson>
</HorizontalListItem>
</div>
</div>
</Container>
<Container css={css`
margin: 88px auto 32px;
padding: 8px;
align-items: flex-start;
flex-direction: column;
`}>
<div css={css`
position: relative;
margin: 0 0 18px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
flex-direction: column;
`}>
<ItemHeader>
{'Surface the things that matter most'}
</ItemHeader>
<div css={css`margin: 0 32px;`}>
<SubHeader>
{'Prioritize the tasks that keep you and your team most productive by organizing your notifications'}
</SubHeader>
</div>
</div>
<DemoScreenshotHeader src={headerPng} />
<DemoScreenshot src={regularScreenshotPng} />
</Container>
</PageContainer>
);

View File

@ -147,7 +147,7 @@ class NotificationsPage extends React.Component {
}
state = {
currentTime: moment().subtract(3, 'd'),
currentTime: moment(),
error: null,
notificationSent: false,
isFirstTimeUser: false,

View File

@ -24,6 +24,9 @@ const FOOTER_HEIGHT = '96px';
const COLLAPSED_WIDTH = '72px';
const EXPANDED_WIDTH = '286px';
const WIDTH_FOR_MEDIUM_SCREENS = '1100px';
const WIDTH_FOR_SMALL_SCREENS = '750px';
// ========================================================================
// START OF 'MOVE TO A UTILS FILE'
// ========================================================================
@ -205,6 +208,9 @@ const MenuHeaderItem = styled(Item)`
border-right: 1px solid #292d35;
background: #2f343e;
z-index: 1;
@media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) {
display: none;
}
`;
const ContentHeaderItem = styled(Item)`
@ -212,6 +218,9 @@ const ContentHeaderItem = styled(Item)`
width: calc(100% - ${COLLAPSED_WIDTH});
border-bottom: 1px solid #E5E6EB;
z-index: 1;
@media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) {
width: 100%;
}
`;
const MenuContainerItem = styled(Item)`
@ -219,6 +228,9 @@ const MenuContainerItem = styled(Item)`
flex: ${({expand}) => expand ? `${EXPANDED_WIDTH} 0 0` : 1};
height: 100%;
transition: all 150ms ease;
@media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) {
display: none;
}
`;
// Faded blue: #F5F6FA
@ -229,6 +241,11 @@ const ContentItem = styled(Item)`
width: calc(100% - ${COLLAPSED_WIDTH});
background: #f7f6f3;
border-left: 1px solid #292d35;
@media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) {
width: 100%;
border-left: 1px solid #f7f6f3;
border-right: 1px solid #f7f6f3;
}
`;
const CardSection = styled('div')`
@ -236,6 +253,9 @@ const CardSection = styled('div')`
display: inline-block;
width: 330px;
padding: 0 16px;
@media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) {
display: none;
}
`;
const Card = styled('div')`
@ -308,6 +328,11 @@ const NotificationsSection = styled('div')`
padding-left: 0;
padding-bottom: 0;
height: auto;
@media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) {
width: calc(100% - (2 * 16px));
padding-right: 16px;
padding-left: 16px;
}
`;
const TitleSection = styled('div')`
@ -346,6 +371,9 @@ const UnorderedList = styled('ul')`
const PageSelection = styled(UnorderedList)`
border-bottom: 2px solid #e5e6eb;
flex-wrap: nowrap;
overflow-x: auto;
overflow-y: hidden;
`;
const SearchField = styled('div')`
@ -461,6 +489,9 @@ const InteractionSection = styled('ul')`
outline: none;
user-select: none;
}
@media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) {
width: 50px;
}
`;
const InteractionMenu = styled('div')`
@ -498,6 +529,9 @@ const InteractionMenu = styled('div')`
}
}
}
@media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) {
left: -240px;
}
`;
const SortingItemComponent = styled('div')`
@ -563,6 +597,9 @@ const NotificationRow = styled(NotificationRowHeader)`
&:active {
background: rgb(252, 250, 248);
};
@media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) {
padding: 6px 2px;
}
`;
const LoadingNotificationRow = styled(NotificationRowHeader)`
@ -617,6 +654,9 @@ const NotificationByline = styled('span')`
margin-top: 4px;
font-size: 12px;
color: #8893a7cc;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
i {
margin-right: 4px;
font-size: 10px;
@ -661,6 +701,9 @@ const ProfileContainer = styled('div')`
color: #bfc5d1
}
}
@media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) {
width: 48px;
}
`;
const ProfileName = styled('span')`
@ -671,6 +714,9 @@ const ProfileName = styled('span')`
overflow: hidden;
text-overflow: ellipsis;
margin-right: auto;
@media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) {
display: none;
}
`;
const ProfilePicture = styled('img')`
@ -1130,8 +1176,8 @@ export default function Scene ({
});
readStatistics = readStatistics.map(n => parseInt(n, 10));
const lastWeekStats = readStatistics.slice(0, 7).map(n => n || null);
const thisWeekStats = readStatistics.slice(7).map(n => n || null);
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);
@ -1170,6 +1216,7 @@ export default function Scene ({
return (
<Container>
{/* Top search & profile bar */}
<Row css={css`
position: fixed;
top: 0;
@ -1221,6 +1268,9 @@ export default function Scene ({
&:hover {
opacity: 0.5;
}
@media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) {
display: none;
}
`}
onClick={() => window.scrollTo(0, 0)}
size={32}
@ -1228,10 +1278,14 @@ export default function Scene ({
<ProfileSection user={user} onLogout={onLogout} />
</ContentHeaderItem>
</Row>
{/* Sidebar options & notifications content */}
<Row css={css`
height: calc(100% - ${COLLAPSED_WIDTH});
margin-top: ${COLLAPSED_WIDTH};
background: #2f343e;
@media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) {
background: ${WHITE};
}
`}>
<MenuContainerItem expand={menuOpen}>
<MenuIconItem
@ -1320,20 +1374,27 @@ export default function Scene ({
<TitleSection>
<Title>{titleOfMode(mode)}</Title>
<InteractionSection>
<li onClick={event => {
event.stopPropagation();
switch(notificationsPermission) {
case 'granted':
return setNotificationsPermission('denied');
case 'denied':
case 'default':
default:
Notification.requestPermission().then(result => {
return setNotificationsPermission(result);
});
}
setDropdownOpen(false);
}}>
<li
css={css`
@media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) {
display: none !important;
}
`}
onClick={event => {
event.stopPropagation();
switch(notificationsPermission) {
case 'granted':
return setNotificationsPermission('denied');
case 'denied':
case 'default':
default:
Notification.requestPermission().then(result => {
return setNotificationsPermission(result);
});
}
setDropdownOpen(false);
}}
>
<IconLink>
{hasNotificationsOn ? (
<i className="fas fa-bell"></i>
@ -1510,12 +1571,14 @@ export default function Scene ({
</>
)}
<IconLink
css={css`@media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) { display: none; }`}
disabled={loading || isFirstPage}
onClick={!loading && !isFirstPage ? (() => onChangePage(page - 1)) : undefined}
>
<i className="fas fa-chevron-left"></i>
</IconLink>
<IconLink
css={css`@media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) { display: none; }`}
disabled={loading || isLastPage}
onClick={!loading && !isLastPage ? (() => onChangePage(page + 1)) : undefined}
>
@ -1551,7 +1614,7 @@ export default function Scene ({
</SortingItem>
</NotificationCell>
{/* Repository */}
<NotificationCell flex={2}>
<NotificationCell flex={2} css={css`@media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) { display: none; }`}>
<SortingItem
sort={Sort.REPOSITORY}
descending={descending}
@ -1575,7 +1638,7 @@ export default function Scene ({
</SortingItem>
</NotificationCell>
{/* Actions */}
<NotificationCell width={70}>
<NotificationCell width={70} css={css`@media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) { display: none; }`}>
<SortingItem
sort={Sort.DATE}
descending={descending}
@ -1613,6 +1676,7 @@ export default function Scene ({
</NotificationsSection>
</ContentItem>
</Row>
{/* Footer links */}
<Row css={css`
height: calc(100% - ${COLLAPSED_WIDTH});
background: #2f343e;
@ -1708,7 +1772,7 @@ function NotificationCollection ({
{notifications.map((item, xid) => (
<AnimatedNotificationRow key={notifications.id || xid}>
{/* Type */}
<NotificationCell width={80}>
<NotificationCell width={60} css={css`@media (max-width: ${WIDTH_FOR_SMALL_SCREENS}) { flex: 50px 0 0; }`}>
{getPRIssueIcon(item.type, item.reasons)}
</NotificationCell>
{/* Title */}
@ -1736,6 +1800,9 @@ function NotificationCollection ({
css={css`
font-weight: 500;
color: #8994A6;
@media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) {
display: none;
}
`}>
{'@' + item.repository}
</NotificationCell>
@ -1754,6 +1821,9 @@ function NotificationCollection ({
text-align: center;
width: 40px;
}
@media (max-width: ${WIDTH_FOR_MEDIUM_SCREENS}) {
display: none;
}
`}>
<ActionItems
item={item}

View File

@ -1,9 +1,7 @@
import React from 'react';
import moment from 'moment';
import {Status} from '../constants/status';
// import {createMockNotifications} from '../utils/mocks';
// const mockNotifications = createMockNotifications(100);
import {createMockNotifications} from '../utils/mocks';
export const LOCAL_STORAGE_PREFIX = '__meteorite_noti_cache__';
export const LOCAL_STORAGE_USER_PREFIX = '__meteorite_user_cache__';
@ -49,19 +47,9 @@ class StorageProvider extends React.Component {
return acc;
}, []);
// @TODO fix this
// Document is out of focus, the we had notifications before this update,
// and there was a change in notifications in the most recent update.
// if (!document.hasFocus() &&
// this.state.notifications.length > 0 &&
// notifications.length !== this.state.notifications.length
// ) {
// this.setTitle('(1) ' + this.originalTitle);
// } else {
// this.setTitle(this.originalTitle);
// }
this.setState({ notifications });
// const mockNotifications = createMockNotifications(20);
// this.setState({ notifications: mockNotifications });
}
@ -81,13 +69,21 @@ class StorageProvider extends React.Component {
* ```
*/
getStat = (stat, startTime = moment(), endTime = moment().add(1, 'day')) => {
const currentTime = moment();
const response = [];
// 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}`);
response.push(value || 0);
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;
response.push(fauxValue);
}
}
return response;
}