mirror of
https://github.com/brianlovin/security-checklist.git
synced 2026-03-11 08:55:31 +00:00
Merge pull request #2 from brianlovin/checklist-setup
First section implemenation pass
This commit is contained in:
commit
dad84eb689
22 changed files with 768 additions and 14 deletions
|
|
@ -8,6 +8,10 @@ type Props = {
|
|||
};
|
||||
|
||||
export default function Card(props: Props) {
|
||||
const { style, children } = props;
|
||||
return <StyledCard style={style}>{children}</StyledCard>;
|
||||
const { style, children, ...rest } = props;
|
||||
return (
|
||||
<StyledCard {...rest} style={style}>
|
||||
{children}
|
||||
</StyledCard>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,4 @@ export const StyledCard = styled.div`
|
|||
background: ${props => props.theme.bg.default};
|
||||
border-radius: 6px;
|
||||
${Shadows.default};
|
||||
|
||||
&:hover {
|
||||
${Shadows.hover};
|
||||
}
|
||||
|
||||
&:active {
|
||||
${Shadows.active};
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
21
components/Checklist/index.js
Normal file
21
components/Checklist/index.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import data from '../../config/data';
|
||||
import ChecklistItem from '../ChecklistItem';
|
||||
import { Grid } from './style';
|
||||
|
||||
class Checklist extends React.Component<{}> {
|
||||
render() {
|
||||
const keys = Object.keys(data);
|
||||
const resources = keys.map(k => data[k]);
|
||||
return (
|
||||
<Grid>
|
||||
{resources.map(resource => (
|
||||
<ChecklistItem key={resource.id} resource={resource} />
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Checklist;
|
||||
9
components/Checklist/style.js
Normal file
9
components/Checklist/style.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// @flow
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Grid = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
margin-top: 32px;
|
||||
width: 100%;
|
||||
`;
|
||||
82
components/ChecklistItem/App.js
Normal file
82
components/ChecklistItem/App.js
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import type { App } from '../../types';
|
||||
import {
|
||||
AppMeta,
|
||||
AppRowContainer,
|
||||
AppIcon,
|
||||
AppName,
|
||||
AppSourcesList,
|
||||
AppSourcesListItem,
|
||||
AppSourcesLabel,
|
||||
} from './style';
|
||||
import Icon from '../Icon';
|
||||
|
||||
type Props = {
|
||||
app: App,
|
||||
};
|
||||
|
||||
export const AppRow = ({ app }: Props) => {
|
||||
const sourcesKeys = Object.keys(app.sources);
|
||||
|
||||
const renderSourceIcon = (key: string) => {
|
||||
const sourceUrl = app.sources[key];
|
||||
const appUrl = app.url;
|
||||
|
||||
if (key === 'windows') {
|
||||
return (
|
||||
<AppSourcesListItem key={key}>
|
||||
<a href={sourceUrl} target="_blank" rel="noopener noreferrer">
|
||||
<Icon size={32} glyph="windows" />
|
||||
<AppSourcesLabel>Windows</AppSourcesLabel>
|
||||
</a>
|
||||
</AppSourcesListItem>
|
||||
);
|
||||
}
|
||||
|
||||
if (key === 'ios') {
|
||||
return (
|
||||
<AppSourcesListItem key={key}>
|
||||
<a href={sourceUrl} target="_blank" rel="noopener noreferrer">
|
||||
<Icon size={32} glyph="apple" />
|
||||
<AppSourcesLabel>iOS</AppSourcesLabel>
|
||||
</a>
|
||||
</AppSourcesListItem>
|
||||
);
|
||||
}
|
||||
|
||||
if (key === 'macos') {
|
||||
return (
|
||||
<AppSourcesListItem key={key}>
|
||||
<a href={sourceUrl} target="_blank" rel="noopener noreferrer">
|
||||
<Icon size={32} glyph="app-store" />
|
||||
<AppSourcesLabel>macOS</AppSourcesLabel>
|
||||
</a>
|
||||
</AppSourcesListItem>
|
||||
);
|
||||
}
|
||||
|
||||
if (key === 'android') {
|
||||
return (
|
||||
<AppSourcesListItem key={key}>
|
||||
<a href={sourceUrl} target="_blank" rel="noopener noreferrer">
|
||||
<Icon size={32} glyph="android" />
|
||||
<AppSourcesLabel>Android</AppSourcesLabel>
|
||||
</a>
|
||||
</AppSourcesListItem>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<AppRowContainer href={app.url} target="_blank" rel="noopener noreferrer">
|
||||
<AppMeta>
|
||||
<AppIcon src={app.image} />
|
||||
<AppName>{app.name}</AppName>
|
||||
</AppMeta>
|
||||
<AppSourcesList>{sourcesKeys.map(renderSourceIcon)}</AppSourcesList>
|
||||
</AppRowContainer>
|
||||
);
|
||||
};
|
||||
20
components/ChecklistItem/Apps.js
Normal file
20
components/ChecklistItem/Apps.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { AppsContainer } from './style';
|
||||
import type { ChecklistResource } from '../../types';
|
||||
import { AppRow } from './App';
|
||||
|
||||
type Props = {
|
||||
resource: ChecklistResource,
|
||||
};
|
||||
|
||||
export const Apps = ({ resource }: Props) => {
|
||||
if (!resource.apps) return null;
|
||||
return (
|
||||
<AppsContainer>
|
||||
{resource.apps.map(app => (
|
||||
<AppRow key={app.name} app={app} />
|
||||
))}
|
||||
</AppsContainer>
|
||||
);
|
||||
};
|
||||
16
components/ChecklistItem/Heading.js
Normal file
16
components/ChecklistItem/Heading.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Title, Description } from './style';
|
||||
import type { ChecklistResource } from '../../types';
|
||||
|
||||
type Props = {
|
||||
resource: ChecklistResource,
|
||||
isChecked: boolean,
|
||||
};
|
||||
|
||||
export const Heading = ({ resource, isChecked }: Props) => (
|
||||
<React.Fragment>
|
||||
<Title>{resource.title}</Title>
|
||||
<Description>{resource.description}</Description>
|
||||
</React.Fragment>
|
||||
);
|
||||
20
components/ChecklistItem/Resource.js
Normal file
20
components/ChecklistItem/Resource.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import type { Resource } from '../../types';
|
||||
import { ResourceRowContainer, ResourceName } from './style';
|
||||
import Icon from '../Icon';
|
||||
|
||||
type Props = {
|
||||
resource: Resource,
|
||||
};
|
||||
|
||||
export const ResourceRow = ({ resource }: Props) => (
|
||||
<ResourceRowContainer
|
||||
href={resource.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Icon glyph="link" size={20} />
|
||||
<ResourceName>{resource.name}</ResourceName>
|
||||
</ResourceRowContainer>
|
||||
);
|
||||
21
components/ChecklistItem/Resources.js
Normal file
21
components/ChecklistItem/Resources.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { SectionHeading } from './style';
|
||||
import type { ChecklistResource } from '../../types';
|
||||
import { ResourceRow } from './Resource';
|
||||
|
||||
type Props = {
|
||||
resource: ChecklistResource,
|
||||
};
|
||||
|
||||
export const Resources = ({ resource }: Props) => {
|
||||
if (!resource.resources) return null;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SectionHeading>More Resources</SectionHeading>
|
||||
{resource.resources.map(r => (
|
||||
<ResourceRow key={r.name} resource={r} />
|
||||
))}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
69
components/ChecklistItem/index.js
Normal file
69
components/ChecklistItem/index.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import type { ChecklistResource } from '../../types';
|
||||
import { getCheckedStatusById, setCheckedStatusById } from './utils';
|
||||
import Card from '../Card';
|
||||
import LoadingChecklistItem from '../LoadingChecklistItem';
|
||||
import { Heading } from './Heading';
|
||||
import { Apps } from './Apps';
|
||||
import { Resources } from './Resources';
|
||||
import {
|
||||
Container,
|
||||
CheckboxContainer,
|
||||
Checkbox,
|
||||
CardContent,
|
||||
ResourceContent,
|
||||
} from './style';
|
||||
|
||||
type Props = {
|
||||
resource: ChecklistResource,
|
||||
};
|
||||
|
||||
type State = {
|
||||
isChecked: boolean,
|
||||
isLoading: boolean,
|
||||
};
|
||||
|
||||
class ChecklistItem extends React.Component<Props, State> {
|
||||
state = { isChecked: false, isLoading: true };
|
||||
|
||||
componentDidMount() {
|
||||
const { resource } = this.props;
|
||||
const isChecked = getCheckedStatusById(resource.id);
|
||||
return this.setState({ isChecked, isLoading: false });
|
||||
}
|
||||
|
||||
handleSetChecked = () => {
|
||||
const { isChecked } = this.state;
|
||||
const { resource } = this.props;
|
||||
setCheckedStatusById(resource.id, !isChecked);
|
||||
return this.setState({ isChecked: !isChecked });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isChecked, isLoading } = this.state;
|
||||
const { resource } = this.props;
|
||||
|
||||
if (isLoading) return <LoadingChecklistItem />;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Card isChecked={isChecked}>
|
||||
<CardContent>
|
||||
<CheckboxContainer onClick={this.handleSetChecked}>
|
||||
<Checkbox isChecked={isChecked} />
|
||||
</CheckboxContainer>
|
||||
|
||||
<ResourceContent isChecked={isChecked}>
|
||||
<Heading resource={resource} isChecked={isChecked} />
|
||||
<Apps resource={resource} />
|
||||
<Resources resource={resource} />
|
||||
</ResourceContent>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChecklistItem;
|
||||
240
components/ChecklistItem/style.js
Normal file
240
components/ChecklistItem/style.js
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
// @flow
|
||||
import styled from 'styled-components';
|
||||
import { theme } from '../theme';
|
||||
import { Shadows } from '../globals';
|
||||
|
||||
export const Container = styled.div`
|
||||
margin-bottom: 24px;
|
||||
`;
|
||||
|
||||
export const CardContent = styled.div`
|
||||
padding: 24px 16px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Title = styled.h3`
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: ${theme.text.default};
|
||||
`;
|
||||
|
||||
export const Description = styled.h4`
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: ${theme.text.secondary};
|
||||
`;
|
||||
|
||||
export const SectionHeading = styled.h5`
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: ${theme.text.tertiary};
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: 32px;
|
||||
`;
|
||||
|
||||
export const CheckboxContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
width: 48px;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
export const Checkbox = styled.span`
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid
|
||||
${props => (props.isChecked ? theme.brand.default : theme.border.default)};
|
||||
background: ${props =>
|
||||
props.isChecked ? theme.brand.default : theme.bg.wash};
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
${Shadows.default};
|
||||
border: 1px solid
|
||||
${props => (props.isChecked ? theme.brand.default : theme.border.active)};
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
left: 11px;
|
||||
top: 6px;
|
||||
width: 6px;
|
||||
height: 12px;
|
||||
border: solid
|
||||
${props => (props.isChecked ? theme.bg.default : theme.border.active)};
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
`;
|
||||
|
||||
export const ResourceContent = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 16px;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding-left: 8px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AppsContainer = styled.div`
|
||||
margin-top: 8px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
margin-top: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AppMeta = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 100%;
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AppRowContainer = styled.a`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
width: 100%;
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
padding-left: 16px;
|
||||
margin-left: -16px;
|
||||
width: calc(100% + 12px);
|
||||
flex-wrap: wrap;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
border-top: 1px solid ${theme.bg.wash};
|
||||
border-radius: 0;
|
||||
margin-top: 0px;
|
||||
margin-left: -24px;
|
||||
width: calc(100% + 40px);
|
||||
padding: 24px;
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: 1px solid ${theme.bg.wash};
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: ${theme.bg.wash};
|
||||
}
|
||||
`;
|
||||
|
||||
export const AppIcon = styled.img`
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
margin-right: 12px;
|
||||
`;
|
||||
|
||||
export const AppName = styled.p`
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: ${theme.text.secondary};
|
||||
`;
|
||||
|
||||
export const AppSourcesList = styled.ul`
|
||||
list-style-type: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 8px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AppSourcesListItem = styled.li`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${theme.text.tertiary};
|
||||
margin-left: 4px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
min-width: 56px;
|
||||
transition: all 0.1s ease-in-out;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
top: 4px;
|
||||
left: 6px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: ${theme.text.secondary};
|
||||
background: ${theme.bg.default};
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AppSourcesLabel = styled.span`
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: ${theme.text.tertiary};
|
||||
margin-top: 4px;
|
||||
`;
|
||||
|
||||
export const ResourceRowContainer = styled.a`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 4px;
|
||||
width: 100%;
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
margin-left: -12px;
|
||||
color: ${theme.text.tertiary};
|
||||
width: calc(100% + 8px);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: calc(100% + 16px);
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: ${theme.bg.wash};
|
||||
color: ${theme.text.default};
|
||||
}
|
||||
`;
|
||||
|
||||
export const ResourceName = styled.p`
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
`;
|
||||
10
components/ChecklistItem/utils.js
Normal file
10
components/ChecklistItem/utils.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// @flow
|
||||
import {
|
||||
getItemFromStorage,
|
||||
storeItem,
|
||||
removeItemFromStorage,
|
||||
} from '../../lib/localStorage';
|
||||
|
||||
export const getCheckedStatusById = (id: string) => getItemFromStorage(id);
|
||||
export const setCheckedStatusById = (id: string, store: boolean) =>
|
||||
store ? storeItem(id, true) : removeItemFromStorage(id);
|
||||
|
|
@ -72,6 +72,33 @@ export const Glyph = ({ glyph }: GlyphProps): any => {
|
|||
<path d="M18.837,27.966c8.342,-0.241 9.163,-1.997 9.163,-11.966c0,-11 -1,-12 -12,-12c-11,0 -12,1 -12,12c0,9.995 0.826,11.734 9.228,11.968c0.073,-0.091 0.1,-0.205 0.1,-0.321c0,-0.25 -0.01,-2.816 -0.015,-3.699c-3.037,0.639 -3.678,-1.419 -3.678,-1.419c-0.497,-1.222 -1.213,-1.548 -1.213,-1.548c-0.991,-0.656 0.075,-0.643 0.075,-0.643c1.096,0.075 1.673,1.091 1.673,1.091c0.974,1.617 2.556,1.15 3.178,0.879c0.099,-0.683 0.381,-1.15 0.693,-1.414c-2.425,-0.267 -4.974,-1.175 -4.974,-5.23c0,-1.155 0.426,-2.099 1.124,-2.839c-0.113,-0.268 -0.487,-1.344 0.107,-2.8c0,0 0.917,-0.285 3.003,1.084c0.871,-0.235 1.805,-0.352 2.734,-0.356c0.927,0.004 1.861,0.121 2.734,0.356c2.085,-1.369 3,-1.084 3,-1.084c0.596,1.456 0.221,2.532 0.108,2.8c0.7,0.74 1.123,1.684 1.123,2.839c0,4.065 -2.553,4.96 -4.986,5.221c0.392,0.327 0.741,0.973 0.741,1.96c0,0.946 -0.006,2.619 -0.01,3.728c-0.002,0.549 -0.003,0.959 -0.003,1.074c0,0.109 0.029,0.224 0.095,0.319Z" />
|
||||
</g>
|
||||
);
|
||||
case 'apple':
|
||||
return (
|
||||
<g>
|
||||
<path d="M16.6437 15.5861C16.3385 16.2848 15.9772 16.928 15.5586 17.5193C14.9881 18.3255 14.5209 18.8835 14.1609 19.1934C13.6027 19.702 13.0048 19.9625 12.3644 19.9773C11.9047 19.9773 11.3504 19.8477 10.705 19.5847C10.0576 19.323 9.46262 19.1934 8.91858 19.1934C8.34801 19.1934 7.73608 19.323 7.08155 19.5847C6.42601 19.8477 5.89793 19.9847 5.49417 19.9983C4.88012 20.0242 4.26806 19.7563 3.65713 19.1934C3.2672 18.8563 2.77947 18.2786 2.1952 17.4601C1.56832 16.586 1.05294 15.5725 0.649179 14.417C0.216767 13.1689 5.70276e-08 11.9603 5.70276e-08 10.7902C5.70276e-08 9.44984 0.292262 8.29382 0.877656 7.32509C1.33773 6.54696 1.94978 5.93315 2.71581 5.48255C3.48185 5.03195 4.30955 4.80232 5.20091 4.78763C5.68863 4.78763 6.32822 4.93713 7.12303 5.23095C7.9156 5.52576 8.42451 5.67526 8.64762 5.67526C8.81444 5.67526 9.37977 5.50045 10.3382 5.15194C11.2445 4.82874 12.0094 4.69492 12.636 4.74763C14.334 4.88343 15.6097 5.54675 16.4581 6.74177C14.9395 7.6536 14.1883 8.93072 14.2032 10.5691C14.2169 11.8452 14.6841 12.9071 15.6022 13.7503C16.0183 14.1417 16.483 14.4441 17 14.6589C16.8879 14.9812 16.7695 15.2898 16.6437 15.5861ZM12.7494 0.400111C12.7494 1.40034 12.3806 2.33425 11.6456 3.19867C10.7586 4.22629 9.68574 4.8201 8.5223 4.7264C8.50747 4.6064 8.49888 4.48011 8.49888 4.3474C8.49888 3.38718 8.9207 2.35956 9.66979 1.51934C10.0438 1.09392 10.5194 0.740188 11.0962 0.458011C11.6718 0.180044 12.2162 0.0263202 12.7282 8.94779e-09C12.7431 0.133712 12.7494 0.267436 12.7494 0.400099V0.400111Z" />
|
||||
</g>
|
||||
);
|
||||
case 'app-store':
|
||||
return (
|
||||
<g>
|
||||
<path d="M0 12.2339V9.24859C0 8.97514 0.224646 8.75147 0.499827 8.75147H5.77522L3.47843 12.7317H0.499827C0.224646 12.7317 0 12.5085 0 12.2339ZM3.75786 18.347L2.07429 19.4876C1.84775 19.6416 1.68755 19.5441 1.71896 19.2723L1.94361 17.3208C1.97486 17.0489 2.19531 16.9379 2.43371 17.0737L3.73663 17.8181C3.97463 17.9546 3.98439 18.1935 3.75786 18.347ZM4.16214 17.247L2.85922 16.5024C2.62102 16.3664 2.53709 16.0613 2.67565 15.824L8.70886 5.36562C8.84574 5.12798 9.15427 5.04466 9.39231 5.18251L10.6952 5.92514C10.9336 6.06298 11.0154 6.36834 10.8788 6.60511L4.84569 17.0642C4.70902 17.3015 4.40024 17.3828 4.16214 17.247ZM8.75382 12.7317L11.0506 8.75147H12.3731L14.2382 12.7317H8.75382ZM16.2343 13.9195C15.7128 13.6591 13.1151 7.69202 12.4495 6.13515C11.7833 4.57823 9.77148 0.433999 10.4222 0.111505C10.8851 -0.119688 12.5471 3.14366 13.7931 5.10282C15.0372 7.06207 17.6389 12.1137 17.9591 12.689C18.2776 13.2657 17.6526 13.7465 17.305 13.8904C16.9571 14.0344 16.7557 14.1779 16.2343 13.9195ZM17.9414 16.6622L17.1565 15.4278C16.9942 15.1709 17.0785 14.8539 17.3417 14.7185L18.0899 14.3354C18.3536 14.2013 18.6838 14.3101 18.8242 14.5787L19.4433 15.7644C19.5842 16.0325 19.4882 16.3767 19.2342 16.5283L18.6993 16.8468C18.4453 17.0002 18.1037 16.9166 17.9414 16.6622ZM20.8301 19.9001C20.6661 19.377 19.414 19.3425 18.8535 18.5549C18.2911 17.77 18.7559 17.3656 18.9511 17.179C21.2247 15.904 20.8301 19.9001 20.8301 19.9001ZM22 12.2339C22 12.5085 21.7754 12.7317 21.5002 12.7317H18.8595C18.8304 12.5782 18.7831 12.4247 18.7034 12.2807C18.6346 12.1582 18.4609 11.8279 18.2188 11.3674C17.8693 10.7029 17.3673 9.7524 16.8299 8.75147H21.4999C21.7752 8.75147 21.9998 8.97514 21.9998 9.24859V12.2339H22Z" />
|
||||
</g>
|
||||
);
|
||||
case 'windows':
|
||||
return (
|
||||
<g>
|
||||
<path d="M0 2.83167L7.7647 1.72197L7.76809 9.58155L0.00709303 9.62793L0 2.83167ZM7.761 10.4871L7.76703 18.3536L0.0060269 17.2338L0.00559175 10.4344L7.761 10.4871ZM8.70226 1.5768L18.9976 0V9.48159L8.70226 9.56736V1.5768ZM19 10.5611L18.9976 20L8.70224 18.4752L8.68781 10.5434L19 10.5611Z" />
|
||||
</g>
|
||||
);
|
||||
case 'android':
|
||||
return (
|
||||
<g>
|
||||
<path d="M1.98508 7.17365H1.92873C1.20629 7.17365 0.617249 7.76456 0.617249 8.48513V14.1951C0.617249 14.9185 1.20629 15.5075 1.92873 15.5075H1.98604C2.70848 15.5075 3.29752 14.9166 3.29752 14.1951V8.48508C3.29656 7.76456 2.70661 7.17365 1.98508 7.17365Z" />
|
||||
<path d="M3.92507 16.399C3.92507 17.0623 4.46711 17.6024 5.13036 17.6024H6.41837V20.6866C6.41837 21.411 7.00928 22 7.72985 22H7.7862C8.5096 22 9.09956 21.41 9.09956 20.6866V17.6024H10.8996V20.6866C10.8996 21.411 11.4923 22 12.2129 22H12.2684C12.9917 22 13.5807 21.41 13.5807 20.6866V17.6024H14.8697C15.532 17.6024 16.074 17.0623 16.074 16.399V7.38312H3.92507V16.399Z" />
|
||||
<path d="M13.0255 1.91553L14.0486 0.33634C14.1143 0.236737 14.0852 0.10055 13.9847 0.0357013C13.8851 -0.0300621 13.7489 -0.00279859 13.684 0.0995919L12.6234 1.73331C11.8277 1.40732 10.939 1.22414 10.0005 1.22414C9.06101 1.22414 8.17416 1.40732 7.37659 1.73331L6.31785 0.0995919C6.25304 -0.00279859 6.11585 -0.0300621 6.01534 0.0357013C5.91482 0.100506 5.88568 0.236737 5.95145 0.33634L6.97544 1.91553C5.1294 2.82023 3.88465 4.52533 3.88465 6.48125C3.88465 6.6015 3.89218 6.71987 3.9025 6.83729H16.0993C16.1097 6.71987 16.1162 6.6015 16.1162 6.48125C16.1163 4.52533 14.8706 2.82023 13.0255 1.91553ZM7.17273 4.74327C6.84861 4.74327 6.58556 4.48209 6.58556 4.15706C6.58556 3.83203 6.84861 3.57177 7.17273 3.57177C7.49871 3.57177 7.75989 3.83199 7.75989 4.15706C7.75989 4.48213 7.49684 4.74327 7.17273 4.74327ZM12.8273 4.74327C12.5032 4.74327 12.2401 4.48209 12.2401 4.15706C12.2401 3.83203 12.5032 3.57177 12.8273 3.57177C13.1523 3.57177 13.4135 3.83199 13.4135 4.15706C13.4135 4.48209 13.1523 4.74327 12.8273 4.74327Z" />
|
||||
<path d="M18.0703 7.17365H18.0158C17.2934 7.17365 16.7025 7.76456 16.7025 8.48513V14.1951C16.7025 14.9185 17.2943 15.5075 18.0158 15.5075H18.0713C18.7946 15.5075 19.3827 14.9166 19.3827 14.1951V8.48508C19.3827 7.76456 18.7928 7.17365 18.0703 7.17365Z" />
|
||||
</g>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
74
components/LoadingChecklistItem/index.js
Normal file
74
components/LoadingChecklistItem/index.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { ShimmerInboxThread, ShimmerBase, ShimmerLine, Cover } from './style';
|
||||
import Card from '../Card';
|
||||
|
||||
const LoadingChecklistItem = () => (
|
||||
<Card>
|
||||
<ShimmerInboxThread>
|
||||
<ShimmerBase>
|
||||
<ShimmerLine />
|
||||
<Cover
|
||||
style={{
|
||||
top: '0',
|
||||
left: '40px',
|
||||
height: '40px',
|
||||
width: '16px',
|
||||
}}
|
||||
/>
|
||||
<Cover
|
||||
style={{
|
||||
top: '0',
|
||||
left: '50%',
|
||||
height: '40px',
|
||||
width: '50%',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Cover
|
||||
style={{
|
||||
top: '20px',
|
||||
left: '40px',
|
||||
height: '8px',
|
||||
width: '50%',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Cover
|
||||
style={{
|
||||
top: '40px',
|
||||
left: '0',
|
||||
height: '16px',
|
||||
width: '100%',
|
||||
}}
|
||||
/>
|
||||
<Cover
|
||||
style={{
|
||||
top: '40px',
|
||||
left: '0',
|
||||
height: '100%',
|
||||
width: '48px',
|
||||
}}
|
||||
/>
|
||||
<Cover
|
||||
style={{
|
||||
top: '72px',
|
||||
left: '48px',
|
||||
height: '8px',
|
||||
width: '100%',
|
||||
}}
|
||||
/>
|
||||
<Cover
|
||||
style={{
|
||||
top: '72px',
|
||||
left: '35%',
|
||||
height: '24px',
|
||||
width: '100%',
|
||||
}}
|
||||
/>
|
||||
</ShimmerBase>
|
||||
</ShimmerInboxThread>
|
||||
</Card>
|
||||
);
|
||||
|
||||
export default LoadingChecklistItem;
|
||||
58
components/LoadingChecklistItem/style.js
Normal file
58
components/LoadingChecklistItem/style.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// @flow
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
import { theme } from '../theme';
|
||||
|
||||
const placeHolderShimmer = keyframes`
|
||||
0%{
|
||||
transform: translateX(-100%) translateY(0%);
|
||||
background-size: 100%;
|
||||
opacity: 1;
|
||||
}
|
||||
100%{
|
||||
transform: translateX(200%) translateY(0%);
|
||||
background-size: 500%;
|
||||
opacity: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ShimmerInboxThread = styled.div`
|
||||
padding: 16px;
|
||||
|
||||
section {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ShimmerBase = styled.section`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
background: ${theme.bg.wash};
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export const ShimmerLine = styled.span`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
animation-duration: 2.5s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: ease-in-out;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
${theme.bg.wash} 10%,
|
||||
${theme.border.active},
|
||||
${theme.bg.wash} 30%
|
||||
);
|
||||
animation-name: ${placeHolderShimmer};
|
||||
`;
|
||||
|
||||
export const Cover = styled.span`
|
||||
position: absolute;
|
||||
background: ${theme.bg.default};
|
||||
`;
|
||||
|
|
@ -48,8 +48,9 @@ export const SectionHeading = styled.div`
|
|||
`;
|
||||
|
||||
export const Heading = styled.h3`
|
||||
font-size: 24px;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
color: ${theme.text.default};
|
||||
|
||||
@media (max-width: 968px) {
|
||||
|
|
@ -58,9 +59,10 @@ export const Heading = styled.h3`
|
|||
`;
|
||||
|
||||
export const Subheading = styled.h4`
|
||||
font-size: 18px;
|
||||
font-size: 22px;
|
||||
font-weight: 400;
|
||||
color: ${theme.text.tertiary};
|
||||
margin-top: 8px;
|
||||
|
||||
a {
|
||||
color: ${theme.text.default};
|
||||
|
|
|
|||
5
config/data.js
Normal file
5
config/data.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import passwordManager from './passwordManager';
|
||||
|
||||
export default {
|
||||
passwordManager,
|
||||
};
|
||||
47
config/passwordManager.js
Normal file
47
config/passwordManager.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
export default {
|
||||
id: 'passwordManager',
|
||||
title: 'Password Manager',
|
||||
description:
|
||||
'A password manager helps you set unique passwords for each service you use. Having a unique password ensures that if one service you use is hacked, the leaked password won’t allow access to all of your accounts.',
|
||||
apps: [
|
||||
{
|
||||
name: '1Password',
|
||||
image: '/static/img/1password.jpg',
|
||||
url: 'https://1password.com/',
|
||||
sources: {
|
||||
windows: 'https://1password.com/downloads/windows/',
|
||||
ios: 'https://1password.com/downloads/ios/',
|
||||
android: 'https://1password.com/downloads/android/',
|
||||
macos: 'https://1password.com/downloads/mac/',
|
||||
website: 'https://1password.com/',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'LastPass',
|
||||
image: '/static/img/lastpass.jpg',
|
||||
url: 'https://www.lastpass.com/',
|
||||
sources: {
|
||||
windows:
|
||||
'https://download.cloud.lastpass.com/windows_installer/lastpass.exe',
|
||||
ios:
|
||||
'https://itunes.apple.com/us/app/lastpass-password-manager/id324613447',
|
||||
android: 'https://lastpass.com/android_market.php',
|
||||
macos:
|
||||
'https://itunes.apple.com/us/app/lastpass/id926036361?ls=1&mt=12',
|
||||
website: 'https://www.lastpass.com/',
|
||||
},
|
||||
},
|
||||
],
|
||||
resources: [
|
||||
{
|
||||
name: 'How to use a password manager (and why you really should)',
|
||||
url:
|
||||
'https://www.theverge.com/2017/7/24/15921282/best-password-manager-1password-lastpass-dashlane-how-to',
|
||||
},
|
||||
{
|
||||
name: 'How password managers work and why you should use one',
|
||||
url:
|
||||
'https://motherboard.vice.com/en_us/article/59yv5x/how-password-managers-work-and-why-you-should-use-one',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import Page, { SectionHeading } from '../components/Page';
|
||||
import Page, { SectionHeading, Heading, Subheading } from '../components/Page';
|
||||
import type { GetInitialProps } from '../types';
|
||||
import Checklist from '../components/Checklist';
|
||||
|
||||
class Index extends React.Component<{}> {
|
||||
static async getInitialProps({ res }: GetInitialProps) {
|
||||
|
|
@ -17,7 +18,15 @@ class Index extends React.Component<{}> {
|
|||
render() {
|
||||
return (
|
||||
<Page>
|
||||
<SectionHeading />
|
||||
<SectionHeading>
|
||||
<Heading>Improve your online privacy and security</Heading>
|
||||
<Subheading>
|
||||
The follow resources are designed to improve everyday people’s
|
||||
security and privacy while using the internet.
|
||||
</Subheading>
|
||||
</SectionHeading>
|
||||
|
||||
<Checklist />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
BIN
static/img/1password.jpg
Normal file
BIN
static/img/1password.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
static/img/lastpass.jpg
Normal file
BIN
static/img/lastpass.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.9 KiB |
|
|
@ -11,3 +11,31 @@ export type GetInitialProps = {
|
|||
getInitialProps?: (ctx: any) => void,
|
||||
},
|
||||
};
|
||||
|
||||
export type Resource = {
|
||||
name: string,
|
||||
url: string,
|
||||
};
|
||||
|
||||
export type AppSource = {
|
||||
windows?: string,
|
||||
ios?: string,
|
||||
macos?: string,
|
||||
anrdroid?: string,
|
||||
website?: string,
|
||||
};
|
||||
|
||||
export type App = {
|
||||
name: string,
|
||||
image: string,
|
||||
sources: AppSource,
|
||||
url: string,
|
||||
};
|
||||
|
||||
export type ChecklistResource = {
|
||||
id: string,
|
||||
title: string,
|
||||
description: string,
|
||||
apps?: ?Array<App>,
|
||||
resources?: ?Array<Resource>,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue