commit
28bc0062b0
48 changed files with 994 additions and 689 deletions
36
README.md
36
README.md
|
|
@ -4,12 +4,11 @@
|
|||
|
||||
A starter project for creating lightning-fast, offline-first websites with [Netlify CMS](https://netlifycms.org) and React.
|
||||
|
||||
- **[Create React App](https://github.com/facebookincubator/create-react-app)**
|
||||
- **[Styled Components](https://github.com/styled-components/styled-components)** for component-based styling
|
||||
- **[React Router](https://github.com/ReactTraining/react-router)** for routing
|
||||
- **[React Helmet](https://github.com/nfl/react-helmet)** for document titles, descriptions, meta
|
||||
- **[React Snapshot](https://github.com/geelen/react-snapshot)** for pre-rendering to static html so it works without Javascript ⭐️
|
||||
- **[Netlify CMS](https://github.com/netlify/netlify-cms)** for content management
|
||||
* **[Create React App](https://github.com/facebookincubator/create-react-app)**
|
||||
* **[React Router](https://github.com/ReactTraining/react-router)** for routing
|
||||
* **[React Helmet](https://github.com/nfl/react-helmet)** for document titles, descriptions, meta
|
||||
* **[React Snapshot](https://github.com/geelen/react-snapshot)** for pre-rendering to static html so it works without Javascript ⭐️
|
||||
* **[Netlify CMS](https://github.com/netlify/netlify-cms)** for content management
|
||||
|
||||
I aim to include commonly used components and best-practices e.g. forms, settings, <head> tags, lazy-loading images, etc.
|
||||
|
||||
|
|
@ -19,41 +18,40 @@ I aim to include commonly used components and best-practices e.g. forms, setting
|
|||
|
||||
1. Hit the **Deploy to Netlify** button. This will:
|
||||
|
||||
- Clone the repo into your Github account
|
||||
- Create you a new project on Netlify, build & deploy
|
||||
* Clone the repo into your Github account
|
||||
* Create you a new project on Netlify, build & deploy
|
||||
|
||||
1. Once your Netlify project has been created, change a couple of settings:
|
||||
|
||||
- Enable **Identity**
|
||||
- Change **Registration Preferences** to **Invite Only**
|
||||
- Enable **Git Gateway**
|
||||
* Enable **Identity**
|
||||
* Change **Registration Preferences** to **Invite Only**
|
||||
* Enable **Git Gateway**
|
||||
|
||||
1. Invite users (probably yourself) to enable admin access
|
||||
|
||||
- Open the **Identity** tab and hit **Invite Users**
|
||||
* Open the **Identity** tab and hit **Invite Users**
|
||||
|
||||
## Show me the CMS!
|
||||
|
||||
The CMS lives at [\__YOUR_SITE_NAME__.netlify.com/admin](https://__YOUR_SITE_NAME__.netlify.com/admin).
|
||||
|
||||
The CMS lives at [\_\_YOUR_SITE_NAME\_\_.netlify.com/admin](https://__YOUR_SITE_NAME__.netlify.com/admin).
|
||||
|
||||
## Developing
|
||||
|
||||
1. Clone your repo to your local machine
|
||||
|
||||
1. Install dependencies
|
||||
1. Install dependencies
|
||||
|
||||
`yarn` or `npm install`
|
||||
`yarn` or `npm install`
|
||||
|
||||
1. Run the development server
|
||||
|
||||
`yarn start` or `npm run start`
|
||||
`yarn start` or `npm run start`
|
||||
|
||||
If you are adding or editing content locally in the CMS, a couple of things to note:
|
||||
|
||||
1. Changes will be pushed to the remote repo.
|
||||
1. Changes will be pushed to the remote repo.
|
||||
|
||||
1. You will be prompted to enter your site's url, this is necessary for Netlify Identity to manage user login. This is stored in `localStorage`, so you might have to empty your browser cache if you are switching projects but remaining on `localhost:3000`.
|
||||
1. You will be prompted to enter your site's url, this is necessary for Netlify Identity to manage user login. This is stored in `localStorage`, so you might have to empty your browser cache if you are switching projects but remaining on `localhost:3000`.
|
||||
|
||||
## Editing CMS fields
|
||||
|
||||
|
|
|
|||
|
|
@ -2,18 +2,18 @@
|
|||
title: Hello World!
|
||||
subtitle: <Home />
|
||||
---
|
||||
|
||||
# 🌶 Netlify CMS + React Starter
|
||||
|
||||
[](http://standardjs.com/)
|
||||
|
||||
A starter project for creating lightning-fast, offline-first websites with [Netlify CMS](https://netlifycms.org) and React.
|
||||
|
||||
- **[Create React App](https://github.com/facebookincubator/create-react-app)**
|
||||
- **[Styled Components](https://github.com/styled-components/styled-components)** for component-based styling
|
||||
- **[React Router](https://github.com/ReactTraining/react-router)** for routing
|
||||
- **[React Helmet](https://github.com/nfl/react-helmet)** for document titles, descriptions, meta
|
||||
- **[React Snapshot](https://github.com/geelen/react-snapshot)** for pre-rendering to static html so it works without Javascript ⭐️
|
||||
- **[Netlify CMS](https://github.com/netlify/netlify-cms)** for content management
|
||||
* **[Create React App](https://github.com/facebookincubator/create-react-app)**
|
||||
* **[React Router](https://github.com/ReactTraining/react-router)** for routing
|
||||
* **[React Helmet](https://github.com/nfl/react-helmet)** for document titles, descriptions, meta
|
||||
* **[React Snapshot](https://github.com/geelen/react-snapshot)** for pre-rendering to static html so it works without Javascript ⭐️
|
||||
* **[Netlify CMS](https://github.com/netlify/netlify-cms)** for content management
|
||||
|
||||
I aim to include commonly used components and best-practices e.g. forms, settings, <head> tags, lazy-loading images, etc.
|
||||
|
||||
|
|
@ -22,34 +22,36 @@ I aim to include commonly used components and best-practices e.g. forms, setting
|
|||
[](https://app.netlify.com/start/deploy?repository=https://github.com/Jinksi/netlify-cms-react-starter)
|
||||
|
||||
1. Hit the **Deploy to Netlify** button. This will:
|
||||
- Clone the repo into your Github account
|
||||
- Create you a new project on Netlify, build & deploy
|
||||
|
||||
* Clone the repo into your Github account
|
||||
* Create you a new project on Netlify, build & deploy
|
||||
|
||||
1. Once your Netlify project has been created, change a couple of settings:
|
||||
- Enable **Identity**
|
||||
- Change **Registration Preferences** to **Invite Only**
|
||||
- Enable **Git Gateway**
|
||||
|
||||
* Enable **Identity**
|
||||
* Change **Registration Preferences** to **Invite Only**
|
||||
* Enable **Git Gateway**
|
||||
|
||||
1. Invite users (probably yourself) to enable admin access
|
||||
- Open the **Identity** tab and hit **Invite Users**
|
||||
* Open the **Identity** tab and hit **Invite Users**
|
||||
|
||||
## Developing
|
||||
|
||||
1. Clone your repo to your local machine
|
||||
|
||||
1. Install dependencies
|
||||
1. Install dependencies
|
||||
|
||||
`yarn` or `npm install`
|
||||
`yarn` or `npm install`
|
||||
|
||||
1. Run the development server
|
||||
|
||||
`yarn start` or `npm run start`
|
||||
`yarn start` or `npm run start`
|
||||
|
||||
If you are adding or editing content locally in the CMS, a couple of things to note:
|
||||
|
||||
- Changes will be pushed to the remote repo.
|
||||
* Changes will be pushed to the remote repo.
|
||||
|
||||
- You will be prompted to enter your site's url, this is necessary for Netlify Identity to manage user login. This is stored in `localStorage`, so you might have to empty your browser cache if you are switching projects but remaining on `localhost:3000`.
|
||||
* You will be prompted to enter your site's url, this is necessary for Netlify Identity to manage user login. This is stored in `localStorage`, so you might have to empty your browser cache if you are switching projects but remaining on `localhost:3000`.
|
||||
|
||||
## Editing CMS fields
|
||||
|
||||
|
|
|
|||
23
package.json
23
package.json
|
|
@ -16,6 +16,7 @@
|
|||
"glob": "^7.1.2",
|
||||
"gray-matter": "^3.1.1",
|
||||
"js-yaml": "^3.10.0",
|
||||
"prop-types": "^15.6.0",
|
||||
"react-scripts": "^1.0.10",
|
||||
"sharp": "^0.18.4",
|
||||
"snazzy": "^7.0.0",
|
||||
|
|
@ -23,33 +24,33 @@
|
|||
"sw-precache": "^5.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@researchgate/react-intersection-observer": "^0.5.0",
|
||||
"@researchgate/react-intersection-observer": "^0.6.0",
|
||||
"core-js": "^2.5.3",
|
||||
"intersection-observer": "^0.5.0",
|
||||
"lodash": "^4.17.4",
|
||||
"netlify-identity-widget": "^1.2.0",
|
||||
"polished": "^1.7.0",
|
||||
"normalize.css": "^7.0.0",
|
||||
"react": "^16.0.0",
|
||||
"react-dom": "^16.0.0",
|
||||
"react-helmet": "^5.1.3",
|
||||
"react-markdown": "^2.5.0",
|
||||
"react-router-dom": "^4.1.1",
|
||||
"react-snapshot": "^1.1.0",
|
||||
"styled-components": "^2.1.0"
|
||||
"react-snapshot": "^1.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "npm run watch:content & react-scripts start",
|
||||
"build": "npm run prepare && react-scripts build && react-snapshot && npm run sw",
|
||||
"start": "npm run watch:content && react-scripts start",
|
||||
"build":
|
||||
"npm run prepare-content && react-scripts build && react-snapshot && npm run sw",
|
||||
"parse-content": "node ./functions/parse-content.js",
|
||||
"resize-images": "node ./functions/resize-images.js",
|
||||
"prepare": "npm run parse-content & npm run resize-images",
|
||||
"watch:content": "chokidar 'content/**/**' -c 'npm run prepare' --initial",
|
||||
"prepare-content": "npm run parse-content && npm run resize-images",
|
||||
"watch:content":
|
||||
"chokidar 'content/**/**' -c 'npm run prepare-content' --initial",
|
||||
"sw": "sw-precache --config='sw-precache-config.js'",
|
||||
"test": "standard | snazzy && react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"reactSnapshot": {
|
||||
"include": [
|
||||
"/404"
|
||||
]
|
||||
"include": ["/404"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Admin tweaks 1.0.2 */
|
||||
/* Admin tweaks 1.0.3 */
|
||||
.nc-mediaLibrary-cardImage {
|
||||
object-fit: contain;
|
||||
height: auto;
|
||||
|
|
@ -33,6 +33,11 @@
|
|||
width: auto;
|
||||
}
|
||||
|
||||
.nc-entryListing-cardImage {
|
||||
background-size: contain;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
.nc-mediaLibrary-cardText {
|
||||
margin: 0;
|
||||
}
|
||||
|
|
@ -41,3 +46,33 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Custom logo */
|
||||
|
||||
/* .nc-githubAuthenticationPage-logo {
|
||||
background-image: url(/images/teeth-on-ferry-logo@2x.png);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 200px;
|
||||
}
|
||||
|
||||
.nc-githubAuthenticationPage-logo svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nc-githubAuthenticationPage-button {
|
||||
text-indent: -1000px;
|
||||
overflow: hidden;
|
||||
width: 8rem;
|
||||
}
|
||||
|
||||
.nc-githubAuthenticationPage-button:after {
|
||||
content: 'Login';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: auto;
|
||||
text-indent: 0;
|
||||
} */
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@
|
|||
<title>Content Manager</title>
|
||||
|
||||
<!-- Include the styles for the Netlify CMS UI, after your own styles -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/netlify-cms@1.0.2/dist/cms.css" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/netlify-cms@1.0.3/dist/cms.css" />
|
||||
<link rel="stylesheet" href="admin.css" />
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<!-- Include the script that builds the page and powers Netlify CMS -->
|
||||
<script src="https://identity-js.netlify.com/v1/netlify-identity-widget.js"></script>
|
||||
<script src="https://unpkg.com/netlify-cms@1.0.2/dist/cms.js"></script>
|
||||
<script src="https://unpkg.com/netlify-cms@1.0.3/dist/cms.js"></script>
|
||||
|
||||
<script>
|
||||
// redirect when logging out
|
||||
|
|
|
|||
BIN
public/images/logo.png
Normal file
BIN
public/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
|
|
@ -14,7 +14,7 @@
|
|||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Netlify CMS + React Starter</title>
|
||||
|
||||
<!-- Place Google Fonts css here -->
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
11
src/App.js
11
src/App.js
|
|
@ -9,9 +9,9 @@ import About from './views/About'
|
|||
import Contact from './views/Contact'
|
||||
import NoMatch from './views/NoMatch'
|
||||
import Nav from './components/Nav'
|
||||
import Footer from './components/Footer'
|
||||
import GithubCorner from './components/GithubCorner'
|
||||
import ServiceWorkerNotifications from './components/ServiceWorkerNotifications'
|
||||
import globalStyles from './globalStyles'
|
||||
import data from './data.json'
|
||||
|
||||
class App extends Component {
|
||||
|
|
@ -19,10 +19,6 @@ class App extends Component {
|
|||
data
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
globalStyles()
|
||||
}
|
||||
|
||||
getDocument = (collection, name) =>
|
||||
this.state.data[collection] &&
|
||||
this.state.data[collection].filter(page => page.name === name)[0]
|
||||
|
|
@ -38,7 +34,7 @@ class App extends Component {
|
|||
} = this.getDocument('settings', 'global')
|
||||
return (
|
||||
<Router>
|
||||
<div>
|
||||
<div className='React-Wrap'>
|
||||
<ScrollToTop />
|
||||
<ServiceWorkerNotifications reloadOnUpdate />
|
||||
<GithubCorner url='https://github.com/Jinksi/netlify-cms-react-starter' />
|
||||
|
|
@ -85,8 +81,9 @@ class App extends Component {
|
|||
/>
|
||||
)}
|
||||
/>
|
||||
<Route component={NoMatch} />
|
||||
<Route render={() => <NoMatch siteUrl={siteUrl} />} />
|
||||
</Switch>
|
||||
<Footer />
|
||||
</div>
|
||||
</Router>
|
||||
)
|
||||
|
|
|
|||
18
src/components/BackgroundImage.css
Normal file
18
src/components/BackgroundImage.css
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
.BackgroundImage {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-position: center;
|
||||
transition: opacity 0.5s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.BackgroundImage-lazy {
|
||||
filter: blur(5px);
|
||||
}
|
||||
|
||||
.BackgroundImage-lazy-loaded {
|
||||
filter: blur(0);
|
||||
}
|
||||
74
src/components/BackgroundImage.js
Normal file
74
src/components/BackgroundImage.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import 'intersection-observer'
|
||||
import Observer from '@researchgate/react-intersection-observer'
|
||||
|
||||
import './BackgroundImage.css'
|
||||
import { getImageSrc } from '../util/getImageUrl'
|
||||
|
||||
export default class BackgroundImage extends React.Component {
|
||||
static defaultProps = {
|
||||
lazy: false,
|
||||
src: ''
|
||||
}
|
||||
|
||||
state = {
|
||||
src:
|
||||
this.props.src.indexOf('http') === 0
|
||||
? ''
|
||||
: getImageSrc(this.props.src, this.props.lazy ? 10 : 1800),
|
||||
dataSrc: getImageSrc(this.props.src, 1800),
|
||||
loaded: false
|
||||
}
|
||||
|
||||
handleIntersection = e => {
|
||||
if (e.isIntersecting) {
|
||||
const img = new Image()
|
||||
img.src = this.state.dataSrc
|
||||
img.onload = () => {
|
||||
this.setState({
|
||||
src: this.state.dataSrc,
|
||||
loaded: true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (this.props.src === nextProps.src) return
|
||||
|
||||
this.setState({
|
||||
src: getImageSrc(nextProps.src, nextProps.lazy ? 10 : 1800),
|
||||
dataSrc: getImageSrc(nextProps.src, 1800)
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
let className = this.props.className || ''
|
||||
if (this.state.loaded) className += ' BackgroundImage-lazy-loaded'
|
||||
if (this.props.lazy) className += ' BackgroundImage-lazy'
|
||||
const options = {
|
||||
onChange: this.handleIntersection,
|
||||
onlyOnce: true,
|
||||
rootMargin: '0% 0% 100%'
|
||||
}
|
||||
|
||||
return (
|
||||
<Observer {...options}>
|
||||
<div
|
||||
className={`BackgroundImage absolute ${className || ''}`}
|
||||
src={this.state.src}
|
||||
style={{
|
||||
backgroundImage: `url(${this.state.src})`,
|
||||
backgroundSize: this.props.contain ? 'contain' : 'cover',
|
||||
opacity: this.props.opacity || 1
|
||||
}}
|
||||
/>
|
||||
</Observer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
BackgroundImage.propTypes = {
|
||||
src: PropTypes.string.isRequired
|
||||
}
|
||||
8
src/components/Content.css
Normal file
8
src/components/Content.css
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
.Content {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.Content--Image {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Marked from 'react-markdown'
|
||||
|
||||
import { getImageSrc, getImageSrcset } from '../util/getImageUrl'
|
||||
import './Content.css'
|
||||
|
||||
export default ({ source }) => (
|
||||
<Content
|
||||
<Marked
|
||||
className='Content'
|
||||
source={source}
|
||||
renderers={{
|
||||
Image: ImageWithSrcset
|
||||
|
|
@ -12,18 +14,12 @@ export default ({ source }) => (
|
|||
/>
|
||||
)
|
||||
|
||||
const Image = styled.img`
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
`
|
||||
const Content = styled(Marked)`
|
||||
white-space: pre-line;
|
||||
`
|
||||
|
||||
const ImageWithSrcset = props => (
|
||||
<Image
|
||||
const ImageWithSrcset = ({ nodeKey, src, alt, ...props }) => (
|
||||
<img
|
||||
className='Content--Image'
|
||||
{...props}
|
||||
src={getImageSrc(props.src)}
|
||||
srcSet={getImageSrcset(props.src)}
|
||||
src={getImageSrc(src)}
|
||||
srcSet={getImageSrcset(src)}
|
||||
alt={alt}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
60
src/components/EnquiryFormSimple.css
Normal file
60
src/components/EnquiryFormSimple.css
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
.EnquiryForm {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
max-width: 500px;
|
||||
margin: 2.5rem 0;
|
||||
}
|
||||
|
||||
.EnquiryForm > * + * {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.EnquiryForm--Label {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.EnquiryForm--Input,
|
||||
.EnquiryForm--Textarea {
|
||||
font-family: inherit;
|
||||
font-weight: 400;
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 1em;
|
||||
line-height: 1;
|
||||
transition: border-color 0.2s;
|
||||
resize: none;
|
||||
background: var(--lightGrey);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.EnquiryForm--Input:-webkit-autofill {
|
||||
-webkit-box-shadow: 0 0 0 1000px var(--lightGrey) inset !important;
|
||||
}
|
||||
|
||||
.EnquiryForm--Input:focus {
|
||||
outline: none;
|
||||
border-color: black;
|
||||
}
|
||||
|
||||
.EnquiryForm--Input[disabled] {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
.EnquiryForm--Input::placeholder {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.EnquiryForm--SubmitButton {
|
||||
width: 90%;
|
||||
margin: 2rem auto 0;
|
||||
}
|
||||
62
src/components/EnquiryFormSimple.js
Normal file
62
src/components/EnquiryFormSimple.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import React from 'react'
|
||||
|
||||
import './EnquiryFormSimple.css'
|
||||
|
||||
export default ({
|
||||
name = 'Simple Form',
|
||||
subject = '', // optional subject of the notification email
|
||||
action = ''
|
||||
}) => (
|
||||
<form
|
||||
className='EnquiryForm'
|
||||
name={name}
|
||||
action={action}
|
||||
data-netlify=''
|
||||
data-netlify-honeypot='_gotcha'
|
||||
>
|
||||
<label className='EnquiryForm--Label'>
|
||||
<input
|
||||
className='EnquiryForm--Input'
|
||||
type='text'
|
||||
placeholder='Name'
|
||||
name='name'
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className='EnquiryForm--Label'>
|
||||
<input
|
||||
className='EnquiryForm--Input'
|
||||
type='phone'
|
||||
placeholder='Phone'
|
||||
name='phone'
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className='EnquiryForm--Label'>
|
||||
<input
|
||||
className='EnquiryForm--Input'
|
||||
type='email'
|
||||
placeholder='Email'
|
||||
name='email'
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className='EnquiryForm--Label'>
|
||||
<textarea
|
||||
className='EnquiryForm--Textarea'
|
||||
placeholder='Message'
|
||||
name='message'
|
||||
rows='10'
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<input type='text' name='_gotcha' style={{ display: 'none' }} />
|
||||
{!!subject && <input type='hidden' name='subject' value={subject} />}
|
||||
<input type='hidden' name='form-name' value={name} />
|
||||
<input
|
||||
className='Button Button--tertiary EnquiryForm--SubmitButton'
|
||||
type='submit'
|
||||
value='Enquire'
|
||||
/>
|
||||
</form>
|
||||
)
|
||||
14
src/components/Footer.css
Normal file
14
src/components/Footer.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
.Footer {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.Footer--Lower {
|
||||
background: var(--primaryDark);
|
||||
padding: 1rem 0;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.Footer--Lower a {
|
||||
color: inherit;
|
||||
}
|
||||
13
src/components/Footer.js
Normal file
13
src/components/Footer.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import React from 'react'
|
||||
|
||||
import './Footer.css'
|
||||
|
||||
export default ({ globalSettings, socialSettings, navLinks }) => (
|
||||
<footer className='Footer'>
|
||||
<div className='Footer--Lower'>
|
||||
<div className='Container taCenter'>
|
||||
<span>© 2017 All rights reserved.</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
48
src/components/GithubCorner.css
Normal file
48
src/components/GithubCorner.css
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
.GithubCorner {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
.GithubCorner svg {
|
||||
fill: #151513;
|
||||
color: #fff;
|
||||
border: 0;
|
||||
max-width: 12.5vmin;
|
||||
max-height: 12.5vmin;
|
||||
}
|
||||
|
||||
.GithubCorner .octo-arm {
|
||||
transform-origin: 130px 106px;
|
||||
}
|
||||
|
||||
.GithubCorner:hover .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out;
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.GithubCorner:hover .octo-arm {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.GithubCorner .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes octocat-wave {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
20%,
|
||||
60% {
|
||||
transform: rotate(-25deg);
|
||||
}
|
||||
|
||||
40%,
|
||||
80% {
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +1,20 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const GithubCorner = styled.a`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
|
||||
svg {
|
||||
fill: #151513;
|
||||
color: #fff;
|
||||
border: 0;
|
||||
max-width: 12.5vmin;
|
||||
max-height: 12.5vmin;
|
||||
}
|
||||
|
||||
.octo-arm {
|
||||
transform-origin: 130px 106px;
|
||||
}
|
||||
|
||||
&:hover .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes octocat-wave {
|
||||
0%, 100% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
20%, 60% {
|
||||
transform: rotate(-25deg);
|
||||
}
|
||||
|
||||
40%, 80% {
|
||||
transform: rotate(10deg)
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:500px) {
|
||||
&:hover .octo-arm {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out;
|
||||
}
|
||||
}
|
||||
`
|
||||
import './GithubCorner.css'
|
||||
|
||||
export default ({ url }) => (
|
||||
<GithubCorner href={url} aria-label='View source on Github'>
|
||||
<a className='GithubCorner' href={url} aria-label='View source on Github'>
|
||||
<svg width='80' height='80' viewBox='0 0 250 250' aria-hidden='true'>
|
||||
<path d='M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z' />
|
||||
<path className='octo-arm' d='M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2' fill='currentColor' />
|
||||
<path className='octo-body' d='M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z' fill='currentColor' />
|
||||
<path
|
||||
className='octo-arm'
|
||||
d='M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path
|
||||
className='octo-body'
|
||||
d='M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
</svg>
|
||||
</GithubCorner>
|
||||
</a>
|
||||
)
|
||||
|
|
|
|||
18
src/components/LazyEmbed.css
Normal file
18
src/components/LazyEmbed.css
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
.LazyEmbed {
|
||||
position: relative;
|
||||
padding-bottom: 56.25%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.LazyEmbed iframe,
|
||||
.LazyEmbed object,
|
||||
.LazyEmbed embed {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
55
src/components/LazyEmbed.js
Normal file
55
src/components/LazyEmbed.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import React from 'react'
|
||||
import 'intersection-observer'
|
||||
import Observer from '@researchgate/react-intersection-observer'
|
||||
|
||||
import './LazyEmbed.css'
|
||||
|
||||
class LazyEmbed extends React.Component {
|
||||
static defaultProps = {
|
||||
lazy: true
|
||||
}
|
||||
|
||||
state = {
|
||||
src: null,
|
||||
dataSrc: this.props.src,
|
||||
loaded: !this.props.lazy
|
||||
}
|
||||
|
||||
handleIntersection = e => {
|
||||
if (e.isIntersecting) {
|
||||
this.setState({
|
||||
src: this.state.dataSrc,
|
||||
loaded: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (this.props.src === nextProps.src) return
|
||||
this.setState({
|
||||
src: nextProps.src,
|
||||
dataSrc: nextProps.src
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
let className = this.props.className || ''
|
||||
if (this.state.loaded) className += ' loaded'
|
||||
const options = {
|
||||
onChange: this.handleIntersection,
|
||||
onlyOnce: true,
|
||||
rootMargin: '0% 0% 100%'
|
||||
}
|
||||
|
||||
return (
|
||||
<Observer {...options}>
|
||||
<div
|
||||
className={`LazyEmbed ${className}`}
|
||||
dangerouslySetInnerHTML={{ __html: this.state.src }}
|
||||
/>
|
||||
</Observer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default LazyEmbed
|
||||
14
src/components/LazyImage.css
Normal file
14
src/components/LazyImage.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
.LazyImage {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 2.5rem;
|
||||
filter: blur(5px);
|
||||
}
|
||||
|
||||
.LazyImage.loaded {
|
||||
filter: blur(0);
|
||||
}
|
||||
|
||||
.LazyImage:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
@ -1,53 +1,85 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import { getImageSrc, getImageSrcset } from '../util/getImageUrl'
|
||||
import 'intersection-observer'
|
||||
import Observer from '@researchgate/react-intersection-observer'
|
||||
|
||||
const Image = styled.img`
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 2.5rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
`
|
||||
import { getImageSrc, getImageSrcset } from '../util/getImageUrl'
|
||||
import './LazyImage.css'
|
||||
|
||||
class LazyImage extends React.Component {
|
||||
static defaultProps = {
|
||||
lazy: false,
|
||||
enableSrcset: true,
|
||||
imageSize: '300'
|
||||
}
|
||||
|
||||
state = {
|
||||
src: getImageSrc(this.props.src, '10'),
|
||||
srcSet: '',
|
||||
dataSrc: getImageSrc(this.props.src, '300'),
|
||||
dataSrcSet: getImageSrcset(this.props.src)
|
||||
src: getImageSrc(
|
||||
this.props.src,
|
||||
this.props.lazy ? '10' : this.props.imageSize
|
||||
),
|
||||
srcSet: this.props.lazy ? '' : getImageSrcset(this.props.src),
|
||||
dataSrc: getImageSrc(this.props.src, this.props.imageSize),
|
||||
dataSrcSet: getImageSrcset(this.props.src),
|
||||
loaded: !this.props.lazy
|
||||
}
|
||||
|
||||
handleIntersection = e => {
|
||||
if (e.isIntersecting) {
|
||||
this.setState({
|
||||
src: this.state.dataSrc,
|
||||
srcSet: this.state.dataSrcSet
|
||||
})
|
||||
const img = new Image()
|
||||
img.src = this.state.dataSrc
|
||||
img.onload = () => {
|
||||
this.setState({
|
||||
src: this.state.dataSrc,
|
||||
srcSet: this.state.dataSrcSet,
|
||||
loaded: true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (this.props.src === nextProps.src) return
|
||||
this.setState({
|
||||
src: getImageSrc(nextProps.src, '10'),
|
||||
srcSet: '',
|
||||
dataSrc: getImageSrc(nextProps.src, '300'),
|
||||
dataSrcSet: getImageSrcset(nextProps.src)
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
const { sizes, alt, className } = this.props
|
||||
const { sizes, lazy, onClick, alt } = this.props
|
||||
let className = this.props.className || ''
|
||||
if (this.state.loaded) className += ' loaded'
|
||||
const options = {
|
||||
onChange: this.handleIntersection,
|
||||
onlyOnce: true,
|
||||
rootMargin: '0% 0% 50%'
|
||||
rootMargin: '0% 0% 100%'
|
||||
}
|
||||
|
||||
if (!lazy) {
|
||||
return (
|
||||
<img
|
||||
className={`LazyImage ${className}`}
|
||||
src={this.state.src}
|
||||
srcSet={this.props.enableSrcset ? this.state.srcSet : undefined}
|
||||
sizes={sizes || '100vw'}
|
||||
onClick={onClick}
|
||||
alt={alt}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Observer {...options}>
|
||||
<Image
|
||||
<img
|
||||
className={`LazyImage ${className}`}
|
||||
src={this.state.src}
|
||||
srcSet={this.state.srcSet}
|
||||
srcSet={this.props.enableSrcset ? this.state.srcSet : undefined}
|
||||
sizes={sizes || '100vw'}
|
||||
onClick={onClick}
|
||||
alt={alt}
|
||||
className={className}
|
||||
/>
|
||||
</Observer>
|
||||
)
|
||||
|
|
|
|||
9
src/components/Logo.css
Normal file
9
src/components/Logo.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.Logo {
|
||||
display: block;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
height: 2rem;
|
||||
width: 2.5rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
|
@ -1,7 +1,12 @@
|
|||
import styled from 'styled-components'
|
||||
import React from 'react'
|
||||
|
||||
export default styled.span`
|
||||
display: block;
|
||||
font-size: 2rem;
|
||||
padding: 0 1rem;
|
||||
`
|
||||
import './Logo.css'
|
||||
|
||||
export default ({ inverted }) => (
|
||||
<div
|
||||
className='Logo'
|
||||
style={{
|
||||
backgroundImage: 'url(/images/logo.png)'
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
6
src/components/Nav.css
Normal file
6
src/components/Nav.css
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.Nav {
|
||||
background: white;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
|
@ -1,27 +1,27 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { Container, Flex } from './common'
|
||||
import NavLink from './NavLink'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import Logo from './Logo'
|
||||
import NavLink from './NavLink'
|
||||
import './Nav.css'
|
||||
|
||||
const Nav = styled.div`
|
||||
background: white;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
`
|
||||
|
||||
export default (props) => (
|
||||
<Nav>
|
||||
<Container>
|
||||
<Flex alignCenter>
|
||||
<Logo>
|
||||
<span role='img' aria-label='Chili'>🌶</span>
|
||||
</Logo>
|
||||
<NavLink to='/' exact>Home</NavLink>
|
||||
<NavLink to='/about/' exact>About</NavLink>
|
||||
<NavLink to='/contact/' exact>Contact</NavLink>
|
||||
</Flex>
|
||||
</Container>
|
||||
</Nav>
|
||||
export default ({ handlePopupOpen }) => (
|
||||
<nav className='Nav'>
|
||||
<div className='Container'>
|
||||
<div className='Flex alignCenter justifyStart'>
|
||||
<Link to='/'>
|
||||
<Logo />
|
||||
</Link>
|
||||
<NavLink to='/' exact>
|
||||
Home
|
||||
</NavLink>
|
||||
<NavLink to='/about/' exact>
|
||||
About
|
||||
</NavLink>
|
||||
<NavLink to='/contact/' exact>
|
||||
Contact
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
|
|
|
|||
17
src/components/NavLink.css
Normal file
17
src/components/NavLink.css
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
.NavLink {
|
||||
padding: 0.5rem 1rem;
|
||||
display: block;
|
||||
font-weight: 400;
|
||||
transition: color 0.2s, border-bottom-color 0.2s;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid;
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
.NavLink:hover,
|
||||
.NavLink:active,
|
||||
.NavLink:focus {
|
||||
color: var(--primary);
|
||||
border-bottom-color: currentColor;
|
||||
}
|
||||
|
|
@ -1,29 +1,10 @@
|
|||
import React from 'react'
|
||||
import { Route, Link } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
|
||||
import { color } from '../globalStyles'
|
||||
import './NavLink.css'
|
||||
|
||||
const NavLink = styled.span`
|
||||
a {
|
||||
padding: .5rem 1rem;
|
||||
display: block;
|
||||
font-weight: 400;
|
||||
transition: color 0.2s, border-bottom-color 0.2s;
|
||||
color: ${props => props.active ? color.primary : 'inherit'};
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid;
|
||||
border-bottom-color: ${props => props.active ? color.primary : 'transparent'};
|
||||
&:hover, &:active, &:focus {
|
||||
color: ${props => props.active ? color.primary : 'inherit'};
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default ({ to, exact, match, children }) => (
|
||||
<Route path={to} exact={exact} children={({match}) => (
|
||||
<NavLink active={match}>
|
||||
<Link to={to}>{children}</Link>
|
||||
</NavLink>
|
||||
)} />
|
||||
export default ({ className, children, ...props }) => (
|
||||
<NavLink {...props} className={`NavLink ${className || ''}`}>
|
||||
{children}
|
||||
</NavLink>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,119 +0,0 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { color } from '../globalStyles'
|
||||
|
||||
export default ({
|
||||
name = 'Simple Form',
|
||||
subject = '', // optional subject of the notification email
|
||||
action = ''
|
||||
}) => (
|
||||
<StyledForm
|
||||
name={name}
|
||||
action={action}
|
||||
data-netlify=''
|
||||
data-netlify-honeypot='_gotcha'
|
||||
>
|
||||
<Label className='Label'>
|
||||
<Input
|
||||
className='Input'
|
||||
type='text'
|
||||
placeholder='Your Name'
|
||||
name='name'
|
||||
required
|
||||
/>
|
||||
</Label>
|
||||
<Label className='Label'>
|
||||
<Input
|
||||
className='Input'
|
||||
type='email'
|
||||
placeholder='Your Email'
|
||||
name='email'
|
||||
required
|
||||
/>
|
||||
</Label>
|
||||
<Label className='Label'>
|
||||
<Textarea
|
||||
className='Input'
|
||||
placeholder='Message'
|
||||
name='message'
|
||||
rows='10'
|
||||
required
|
||||
/>
|
||||
</Label>
|
||||
<Input type='text' name='_gotcha' style={{ display: 'none' }} />
|
||||
{!!subject && <Input type='hidden' name='subject' value={subject} />}
|
||||
<Input type='hidden' name='form-name' value={name} />
|
||||
<Button className='button' type='submit' value='Send' />
|
||||
</StyledForm>
|
||||
)
|
||||
|
||||
const StyledForm = styled.form`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
max-width: 500px;
|
||||
margin: 2.5rem 0;
|
||||
|
||||
& > * + * {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
`
|
||||
const Label = styled.label`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
|
||||
const Input = styled.input`
|
||||
font-family: inherit;
|
||||
font-weight: 400;
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 0;
|
||||
border: none;
|
||||
border-bottom: 1px solid #bababa;
|
||||
padding: 0.5em 0;
|
||||
line-height: 1;
|
||||
transition: border-color 0.2s;
|
||||
resize: none;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: black;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
cursor: progress;
|
||||
}
|
||||
`
|
||||
const Textarea = Input.withComponent('textarea')
|
||||
const Button = styled.input`
|
||||
background: ${color.primary};
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.15em;
|
||||
font-size: 1.4rem;
|
||||
display: inline-block;
|
||||
padding: 1rem 2rem;
|
||||
border: none;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: white;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
cursor: progress;
|
||||
}
|
||||
`
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import styled from 'styled-components'
|
||||
|
||||
export default styled.div`
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
`
|
||||
13
src/components/PageHeader.css
Normal file
13
src/components/PageHeader.css
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
.PageHeader {
|
||||
line-height: 1em;
|
||||
color: var(--primary);
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.PageHeader--Title {
|
||||
}
|
||||
|
||||
.PageHeader--Subtitle {
|
||||
font-weight: 200;
|
||||
}
|
||||
|
|
@ -1,35 +1,17 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { Section, Container, BackgroundImage } from './common'
|
||||
import { getImageSrc } from '../util/getImageUrl'
|
||||
import { color } from '../globalStyles'
|
||||
|
||||
const Header = styled(Section)`
|
||||
line-height: 1em;
|
||||
color: ${color.primary};
|
||||
background: ${color.primary};
|
||||
color: white;
|
||||
|
||||
h2 {
|
||||
font-weight: 200;
|
||||
}
|
||||
`
|
||||
import BackgroundImage from './BackgroundImage'
|
||||
import './PageHeader.css'
|
||||
|
||||
const PageHeader = ({ title, subtitle, backgroundImage }) => (
|
||||
<Header relative>
|
||||
{backgroundImage && (
|
||||
<BackgroundImage
|
||||
image={getImageSrc(backgroundImage, '1200')}
|
||||
opacity={0.5}
|
||||
/>
|
||||
)}
|
||||
<Container relative>
|
||||
<h1>{title}</h1>
|
||||
{subtitle ? <h2>{subtitle}</h2> : ''}
|
||||
</Container>
|
||||
</Header>
|
||||
<div className='Section PageHeader relative'>
|
||||
{backgroundImage && <BackgroundImage src={backgroundImage} opacity={0.5} />}
|
||||
<div className='Container relative'>
|
||||
<h1 className='PageHeader--Title'>{title}</h1>
|
||||
{subtitle ? <h2 className='PageHeader--Subtitle'>{subtitle}</h2> : ''}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
PageHeader.propTypes = {
|
||||
|
|
|
|||
|
|
@ -1,40 +1,4 @@
|
|||
import React, { Component } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { color } from '../globalStyles'
|
||||
|
||||
const Notification = styled.div`
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
background: ${color.primary};
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
min-height: 5rem;
|
||||
transition: transform 0.1s ease-in-out;
|
||||
transform: translateY(${props => (props.message ? '0' : '100%')});
|
||||
`
|
||||
const CloseButton = styled.button.attrs({
|
||||
children: 'x'
|
||||
})`
|
||||
border: none;
|
||||
background: white;
|
||||
color: ${color.primary};
|
||||
border-radius: 100%;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 1rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
opacity: 0.7;
|
||||
outline: none;
|
||||
}
|
||||
`
|
||||
import { Component } from 'react'
|
||||
|
||||
export default class ServiceWorkerNotifications extends Component {
|
||||
static defaultProps = {
|
||||
|
|
@ -93,11 +57,6 @@ export default class ServiceWorkerNotifications extends Component {
|
|||
|
||||
render () {
|
||||
this.props.reloadOnUpdate && this.reloadIfUpdated()
|
||||
return this.state.message ? (
|
||||
<Notification message={this.state.message}>
|
||||
{this.state.message}
|
||||
<CloseButton onClick={this.handleDismiss} />
|
||||
</Notification>
|
||||
) : null
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
import styled, { css } from 'styled-components'
|
||||
|
||||
const mixin = {
|
||||
position: css`
|
||||
${props => {
|
||||
if (props.relative) return 'position: relative;'
|
||||
if (props.absolute) return 'position: absolute;'
|
||||
}};
|
||||
`,
|
||||
textAlign: css`
|
||||
${props => {
|
||||
if (props.taCenter) return 'text-align: center;'
|
||||
if (props.taLeft) return 'text-align: left;'
|
||||
if (props.taRight) return 'text-align: right;'
|
||||
}};
|
||||
`
|
||||
}
|
||||
|
||||
export const Absolute = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
`
|
||||
|
||||
export const Relative = styled.div`
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
`
|
||||
|
||||
export const Section = styled.section`
|
||||
${mixin.position}
|
||||
|
||||
width: 100%;
|
||||
padding: ${props => {
|
||||
if (props.thick) return '10rem 0'
|
||||
if (props.thin) return '2.5rem 0'
|
||||
return '5rem 0'
|
||||
}};
|
||||
`
|
||||
|
||||
export const Container = styled.div`
|
||||
${mixin.position}
|
||||
${mixin.textAlign}
|
||||
|
||||
margin: 0 auto;
|
||||
width: 90vw;
|
||||
max-width: ${props => {
|
||||
if (props.skinny) return '888px'
|
||||
if (props.skinnier) return '555px'
|
||||
return '1111px'
|
||||
}};
|
||||
`
|
||||
|
||||
export const Flex = styled.div`
|
||||
display: flex;
|
||||
flex-direction: ${props => props.column ? 'column' : 'row'};
|
||||
justify-content: ${props => {
|
||||
if (props.justifyCenter) return 'center'
|
||||
if (props.justifyEnd) return 'flex-end'
|
||||
if (props.justifyBetween) return 'space-between'
|
||||
if (props.justifyAround) return 'space-around'
|
||||
return 'flex-start'
|
||||
}};
|
||||
align-items: ${props => {
|
||||
if (props.alignStart) return 'flex-start'
|
||||
if (props.alignEnd) return 'flex-end'
|
||||
if (props.alignStretch) return 'stretch'
|
||||
return 'center'
|
||||
}};
|
||||
height: ${props => props.fill ? '100%' : 'auto'};
|
||||
width: ${props => props.fill ? '100%' : 'auto'};
|
||||
`
|
||||
|
||||
export const BackgroundImage = styled(Absolute)`
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-image: url(${props => props.image});
|
||||
opacity: ${props => props.opacity || 1};
|
||||
transition: opacity .5s ease;
|
||||
`
|
||||
254
src/globalStyles.css
Normal file
254
src/globalStyles.css
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
:root {
|
||||
--primary: #ff3d3d;
|
||||
--secondary: #212121;
|
||||
--lightGrey: whitesmoke;
|
||||
--midGrey: #cacaca;
|
||||
--font-primary: 'Open Sans', sans-serif;
|
||||
--font-system: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto',
|
||||
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
font-size: 62.5%;
|
||||
min-height: 100%;
|
||||
background: var(--lightGrey);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-system);
|
||||
min-height: 100%;
|
||||
position: relative;
|
||||
background: white;
|
||||
color: var(--secondary);
|
||||
font-size: 1.6em;
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.01em;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin: 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: var(--lightGrey);
|
||||
line-height: 1.45;
|
||||
font-size: 85%;
|
||||
border-radius: 3px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 85%;
|
||||
padding: 0.15em 0;
|
||||
background: var(--lightGrey);
|
||||
border-radius: 3px;
|
||||
}
|
||||
code:before,
|
||||
code:after {
|
||||
letter-spacing: -0.2em;
|
||||
content: '\00a0';
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
}
|
||||
pre code:before,
|
||||
pre code:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Utility Classes */
|
||||
.light {
|
||||
background-color: var(--lightGrey);
|
||||
}
|
||||
.white {
|
||||
background-color: white;
|
||||
}
|
||||
.dark {
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
.dark h1,
|
||||
.dark h2,
|
||||
.dark h3,
|
||||
.dark h4,
|
||||
.dark h5,
|
||||
.dark h6 {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
.absolute {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.taCenter {
|
||||
text-align: center;
|
||||
}
|
||||
.taLeft {
|
||||
text-align: left;
|
||||
}
|
||||
.taRight {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
|
||||
.Section {
|
||||
width: 100%;
|
||||
padding: 5rem 0;
|
||||
}
|
||||
.Section.thick {
|
||||
padding: 10rem 0;
|
||||
}
|
||||
.Section.thin {
|
||||
padding: 2.5rem 0;
|
||||
}
|
||||
.Section.noPadding {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.Container {
|
||||
max-width: 1111px;
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.Container.skinny {
|
||||
max-width: 888px;
|
||||
}
|
||||
.Container.skinnier {
|
||||
max-width: 555px;
|
||||
}
|
||||
|
||||
/* Flex */
|
||||
|
||||
.Flex {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
.Flex.column {
|
||||
flex-direction: column;
|
||||
}
|
||||
.Flex.justifyCenter {
|
||||
justify-content: center;
|
||||
}
|
||||
.Flex.justifyEnd {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.Flex.justifyBetween {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.Flex.justifyAround {
|
||||
justify-content: space-around;
|
||||
}
|
||||
.Flex.alignStart {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.Flex.alignEnd {
|
||||
align-items: flex-end;
|
||||
}
|
||||
.Flex.alignStretch {
|
||||
align-items: stretch;
|
||||
}
|
||||
.Flex.fill {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
|
||||
.Button {
|
||||
border-radius: none;
|
||||
display: inline-block;
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
padding: 1.5rem;
|
||||
border: 1px solid;
|
||||
border-radius: 0.1rem;
|
||||
border-color: var(--primary);
|
||||
text-decoration: none;
|
||||
font-size: 1.8rem;
|
||||
line-height: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
max-width: 35rem;
|
||||
}
|
||||
.Button:hover,
|
||||
.Button:focus {
|
||||
background: white;
|
||||
border-color: currentColor;
|
||||
color: var(--tertiary);
|
||||
}
|
||||
.Button[disabled] {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
cursor: progress;
|
||||
}
|
||||
.Button.Button--tertiary {
|
||||
background: var(--tertiary);
|
||||
border-color: var(--tertiary);
|
||||
}
|
||||
.Button.Button--tertiary:hover,
|
||||
.Button.Button--tertiary:focus {
|
||||
background: transparent;
|
||||
border-color: currentColor;
|
||||
color: var(--tertiary);
|
||||
}
|
||||
.Button.Button--inverted {
|
||||
background: white;
|
||||
border-color: white;
|
||||
color: var(--tertiary);
|
||||
}
|
||||
.Button.Button--inverted:hover,
|
||||
.Button.Button--inverted:focus {
|
||||
background: transparent;
|
||||
border-color: currentColor;
|
||||
color: white;
|
||||
background-image: linear-gradient(rgba(255, 255, 255, 0.1), rgba(0, 0, 0, 0)),
|
||||
linear-gradient(
|
||||
-32deg,
|
||||
rgba(255, 255, 255, 0),
|
||||
rgba(255, 255, 255, 0.05) 50%,
|
||||
rgba(255, 255, 255, 0) 50%,
|
||||
rgba(255, 255, 255, 0)
|
||||
);
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
import { injectGlobal } from 'styled-components'
|
||||
import { normalize } from 'polished'
|
||||
|
||||
export const font = {
|
||||
primary: `'Open Sans', sans-serif`,
|
||||
system: `-apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", "Roboto", "Oxygen",
|
||||
"Ubuntu", "Cantarell", "Fira Sans",
|
||||
"Droid Sans", "Helvetica Neue", sans-serif`
|
||||
}
|
||||
|
||||
export const color = {
|
||||
primary: '#ff3d3d',
|
||||
secondary: '#212121',
|
||||
lightGrey: 'whitesmoke'
|
||||
}
|
||||
|
||||
export default () => injectGlobal`
|
||||
${normalize()}
|
||||
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
font-size: 62.5%;
|
||||
background: ${color.lightGrey};
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: ${font.system};
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
background: white;
|
||||
color: ${color.secondary};
|
||||
font-size: 1.6em;
|
||||
font-weight: 300;
|
||||
letter-spacing: .01em;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a {
|
||||
color: ${color.primary};
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5 ,h6{
|
||||
margin: 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: ${color.lightGrey};
|
||||
line-height: 1.45;
|
||||
font-size: 85%;
|
||||
border-radius: 3px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 85%;
|
||||
padding: 0.15em 0;
|
||||
background: ${color.lightGrey};
|
||||
border-radius: 3px;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
letter-spacing: -0.2em;
|
||||
content: "\00a0";
|
||||
}
|
||||
|
||||
pre & {
|
||||
font-size: inherit;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
import 'core-js/es6'
|
||||
import React from 'react'
|
||||
import { render } from 'react-snapshot'
|
||||
import App from './App'
|
||||
import registerServiceWorker from './registerServiceWorker'
|
||||
import './netlifyIdentity'
|
||||
import 'normalize.css'
|
||||
import './globalStyles.css'
|
||||
|
||||
const rootEl = document.getElementById('root')
|
||||
render(<App />, rootEl)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const parseFilename = filename => {
|
|||
}
|
||||
|
||||
const getImageSrcset = path => {
|
||||
if (path.match(/^http/) || path.match(/svg$/)) return null
|
||||
if (!path || path.match(/^http/) || path.match(/svg$/) || window.CMS) { return null }
|
||||
const { filename, extname } = parseFilename(path)
|
||||
const pathname = encodeURI(filename.replace(outputDir, resizedDir))
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ const getImageSrcset = path => {
|
|||
}
|
||||
|
||||
const getImageSrc = (path, sizeRequested) => {
|
||||
if (path.match(/^http/) || path.match(/svg$/)) return path
|
||||
if (!path || path.match(/^http/) || path.match(/svg$/) || window.CMS) { return path }
|
||||
sizeRequested = parseInt(sizeRequested, 10)
|
||||
let size
|
||||
if (sizeRequested) {
|
||||
|
|
|
|||
2
src/views/About.css
Normal file
2
src/views/About.css
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.About {
|
||||
}
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
import React from 'react'
|
||||
import Helmet from 'react-helmet'
|
||||
|
||||
import Page from '../components/Page'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import LazyImage from '../components/LazyImage'
|
||||
import { Container, Section } from '../components/common'
|
||||
import Content from '../components/Content.js'
|
||||
import './About.css'
|
||||
|
||||
export default ({ page }) => (
|
||||
<Page>
|
||||
<div className='About'>
|
||||
<Helmet>
|
||||
<title>{page.title}</title>
|
||||
</Helmet>
|
||||
|
|
@ -17,17 +16,17 @@ export default ({ page }) => (
|
|||
subtitle={page.subtitle}
|
||||
backgroundImage={page.featuredImage}
|
||||
/>
|
||||
<Section thin>
|
||||
<Container>
|
||||
<div className='Section thin'>
|
||||
<div className='Container'>
|
||||
<Content source={page.section1} />
|
||||
</Container>
|
||||
</Section>
|
||||
<Section thin>
|
||||
<Container>
|
||||
</div>
|
||||
</div>
|
||||
<div className='Seciton thin'>
|
||||
<div className='Container'>
|
||||
<Content source={page.section2} />
|
||||
<p>The image below is a {'<LazyImage />'}</p>
|
||||
<LazyImage src={page.featuredImage} alt='LazyImage' />
|
||||
</Container>
|
||||
</Section>
|
||||
</Page>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
2
src/views/Contact.css
Normal file
2
src/views/Contact.css
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.Contact {
|
||||
}
|
||||
|
|
@ -1,31 +1,31 @@
|
|||
import React from 'react'
|
||||
import Helmet from 'react-helmet'
|
||||
import Page from '../components/Page'
|
||||
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import NetlifyControlledForm from '../components/NetlifyControlledForm'
|
||||
import NetlifySimpleForm from '../components/NetlifySimpleForm'
|
||||
import { Container, Section } from '../components/common'
|
||||
import Marked from 'react-markdown'
|
||||
// import NetlifyControlledForm from '../components/NetlifyControlledForm'
|
||||
import EnquiryFormSimple from '../components/EnquiryFormSimple'
|
||||
import Content from '../components/Content'
|
||||
import './Contact.css'
|
||||
|
||||
export default ({ page }) => (
|
||||
<Page>
|
||||
<div className='Contact'>
|
||||
<PageHeader title={page.title} subtitle='<Contact />' />
|
||||
<Section thin>
|
||||
<Container>
|
||||
<Marked source={page.content} />
|
||||
<div className='Section thin'>
|
||||
<div className='Container'>
|
||||
<Content source={page.content} />
|
||||
<br />
|
||||
<h3>{'<NetlifyControlledForm />'}</h3>
|
||||
<NetlifyControlledForm name={'Controlled Form'} />
|
||||
{/* <NetlifyControlledForm name={'Controlled Form'} /> */}
|
||||
<br />
|
||||
<h3>{'<NetlifySimpleForm />'}</h3>
|
||||
<NetlifySimpleForm name='Simple Form' />
|
||||
<EnquiryFormSimple name='Simple Form' />
|
||||
<em>Note: these will only work when deployed on Netlify</em>
|
||||
<br />
|
||||
<em>Also, they are active and I will receive submissions</em> 😉
|
||||
</Container>
|
||||
</Section>
|
||||
</div>
|
||||
</div>
|
||||
<Helmet>
|
||||
<title>{page.title}</title>
|
||||
</Helmet>
|
||||
</Page>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
2
src/views/Home.css
Normal file
2
src/views/Home.css
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.Home {
|
||||
}
|
||||
|
|
@ -1,23 +1,23 @@
|
|||
import React from 'react'
|
||||
import Helmet from 'react-helmet'
|
||||
import Page from '../components/Page'
|
||||
import { Container, Section } from '../components/common'
|
||||
|
||||
import Content from '../components/Content'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import './Home.css'
|
||||
|
||||
export default ({ page }) => {
|
||||
const { title, subtitle } = page
|
||||
return (
|
||||
<Page>
|
||||
<main className='Home'>
|
||||
<PageHeader title={title} subtitle={subtitle} />
|
||||
<Section thin>
|
||||
<Container>
|
||||
<div className='Section thin'>
|
||||
<div className='Container'>
|
||||
<Content source={page.content} />
|
||||
</Container>
|
||||
</Section>
|
||||
</div>
|
||||
</div>
|
||||
<Helmet>
|
||||
<title>{title}</title>
|
||||
</Helmet>
|
||||
</Page>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
6
src/views/NoMatch.css
Normal file
6
src/views/NoMatch.css
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.body--NoMatch .React-Wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
|
@ -1,19 +1,31 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Helmet from 'react-helmet'
|
||||
import Page from '../components/Page'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import { Section, Container } from '../components/common'
|
||||
|
||||
export default () => (
|
||||
<Page>
|
||||
<PageHeader title='404' subtitle='<NoMatch />' />
|
||||
<Section>
|
||||
<Container taCenter>
|
||||
<h3>404 – Page Not Found</h3>
|
||||
</Container>
|
||||
</Section>
|
||||
import './NoMatch.css'
|
||||
|
||||
const NoMatch = ({ siteUrl }) => (
|
||||
<div className='NoMatch'>
|
||||
<section className='Section thick'>
|
||||
<div className='Container taCenter'>
|
||||
<h1>404 - Page Not Found</h1>
|
||||
<p>
|
||||
We can't find the page you are looking for!<br />Head back to{' '}
|
||||
<a href={siteUrl}>
|
||||
{siteUrl.replace(/(^https?:\/\/)/, '').replace(/\/$/, '')}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<Helmet>
|
||||
<title>404 – Page Not Found</title>
|
||||
<body className='body--NoMatch' />
|
||||
</Helmet>
|
||||
</Page>
|
||||
</div>
|
||||
)
|
||||
|
||||
NoMatch.propTypes = {
|
||||
siteUrl: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default NoMatch
|
||||
|
|
|
|||
|
|
@ -3,11 +3,9 @@ module.exports = {
|
|||
stripPrefix: 'build/',
|
||||
staticFileGlobs: [
|
||||
'build/**/*.html',
|
||||
'build/**/*.svg',
|
||||
'build/images/*.jpg',
|
||||
'build/images/uploads/resized/*.jpg',
|
||||
'build/**/*.png',
|
||||
'build/**/*.gif',
|
||||
'build/**/*.svg', // all svgs
|
||||
'build/images/*.+(jpg|png|gif)',
|
||||
'build/images/uploads/**/*10.+(jpg|png|gif)', // 10px resized images
|
||||
'build/**/*.ico',
|
||||
'build/**/*.json',
|
||||
'build/static/**/!(*map*)',
|
||||
|
|
|
|||
75
yarn.lock
75
yarn.lock
|
|
@ -54,9 +54,9 @@
|
|||
lodash "^4.2.0"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@researchgate/react-intersection-observer@^0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@researchgate/react-intersection-observer/-/react-intersection-observer-0.5.0.tgz#c83341fe2d86467816373e9141c37b71b90798d9"
|
||||
"@researchgate/react-intersection-observer@^0.6.0":
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@researchgate/react-intersection-observer/-/react-intersection-observer-0.6.0.tgz#505f20b62c8941a035442b61ac448870afb5d8a2"
|
||||
dependencies:
|
||||
invariant "^2.2.2"
|
||||
prop-types "^15.6.0"
|
||||
|
|
@ -1260,13 +1260,6 @@ buffer@^4.3.0:
|
|||
ieee754 "^1.1.4"
|
||||
isarray "^1.0.0"
|
||||
|
||||
buffer@^5.0.3:
|
||||
version "5.0.8"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.0.8.tgz#84daa52e7cf2fa8ce4195bc5cf0f7809e0930b24"
|
||||
dependencies:
|
||||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
|
||||
builtin-modules@^1.0.0, builtin-modules@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
|
||||
|
|
@ -1687,6 +1680,10 @@ core-js@^2.4.0, core-js@^2.5.0:
|
|||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b"
|
||||
|
||||
core-js@^2.5.3:
|
||||
version "2.5.3"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
|
||||
|
||||
core-util-is@1.0.2, core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
|
|
@ -1772,10 +1769,6 @@ crypto-browserify@^3.11.0:
|
|||
randombytes "^2.0.0"
|
||||
randomfill "^1.0.3"
|
||||
|
||||
css-color-keywords@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
|
||||
|
||||
css-color-names@0.0.4:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
|
||||
|
|
@ -1816,14 +1809,6 @@ css-selector-tokenizer@^0.7.0:
|
|||
fastparse "^1.1.1"
|
||||
regexpu-core "^1.0.0"
|
||||
|
||||
css-to-react-native@^2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.0.4.tgz#cf4cc407558b3474d4ba8be1a2cd3b6ce713101b"
|
||||
dependencies:
|
||||
css-color-keywords "^1.0.0"
|
||||
fbjs "^0.8.5"
|
||||
postcss-value-parser "^3.3.0"
|
||||
|
||||
css-what@2.1:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd"
|
||||
|
|
@ -2840,7 +2825,7 @@ fb-watchman@^2.0.0:
|
|||
dependencies:
|
||||
bser "^2.0.0"
|
||||
|
||||
fbjs@^0.8.16, fbjs@^0.8.5, fbjs@^0.8.9:
|
||||
fbjs@^0.8.16:
|
||||
version "0.8.16"
|
||||
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
|
||||
dependencies:
|
||||
|
|
@ -3376,10 +3361,6 @@ hoek@4.x.x:
|
|||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
|
||||
|
||||
hoist-non-react-statics@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
|
||||
|
||||
hoist-non-react-statics@^2.3.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
|
||||
|
|
@ -3729,10 +3710,6 @@ is-fullwidth-code-point@^2.0.0:
|
|||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
|
||||
|
||||
is-function@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5"
|
||||
|
||||
is-glob@^2.0.0, is-glob@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
|
||||
|
|
@ -3798,12 +3775,6 @@ is-plain-obj@^1.0.0:
|
|||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
|
||||
|
||||
is-plain-object@^2.0.1:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
|
||||
dependencies:
|
||||
isobject "^3.0.1"
|
||||
|
||||
is-posix-bracket@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
|
||||
|
|
@ -3892,10 +3863,6 @@ isobject@^2.0.0:
|
|||
dependencies:
|
||||
isarray "1.0.0"
|
||||
|
||||
isobject@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||
|
||||
isomorphic-fetch@^2.1.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
|
||||
|
|
@ -4857,6 +4824,10 @@ normalize-url@^1.4.0:
|
|||
query-string "^4.1.0"
|
||||
sort-keys "^1.0.0"
|
||||
|
||||
normalize.css@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-7.0.0.tgz#abfb1dd82470674e0322b53ceb1aaf412938e4bf"
|
||||
|
||||
npm-conf@^1.1.0:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9"
|
||||
|
|
@ -5230,10 +5201,6 @@ pluralize@^7.0.0:
|
|||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
|
||||
|
||||
polished@^1.7.0:
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/polished/-/polished-1.9.0.tgz#76d277d6610dbcb85477dbba1bd1aa8e642741b6"
|
||||
|
||||
portfinder@^1.0.9:
|
||||
version "1.0.13"
|
||||
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
|
||||
|
|
@ -6722,24 +6689,6 @@ style-loader@0.19.0:
|
|||
loader-utils "^1.0.2"
|
||||
schema-utils "^0.3.0"
|
||||
|
||||
styled-components@^2.1.0:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-2.2.3.tgz#154575c269880c840f903f580287dab155cf684c"
|
||||
dependencies:
|
||||
buffer "^5.0.3"
|
||||
css-to-react-native "^2.0.3"
|
||||
fbjs "^0.8.9"
|
||||
hoist-non-react-statics "^1.2.0"
|
||||
is-function "^1.0.1"
|
||||
is-plain-object "^2.0.1"
|
||||
prop-types "^15.5.4"
|
||||
stylis "3.x"
|
||||
supports-color "^3.2.3"
|
||||
|
||||
stylis@3.x:
|
||||
version "3.4.3"
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.4.3.tgz#875bd0db3db37bb6de08f89275fc38ee2e32ee75"
|
||||
|
||||
supports-color@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue