├── .gitignore ├── .nvmrc ├── LICENSE ├── README.md ├── gatsby-config.js ├── gatsby-node.js ├── netlify.toml ├── package.json ├── renovate.json ├── src ├── cms │ ├── cms.js │ └── preview-templates │ │ ├── AboutPagePreview.js │ │ ├── FooterPreview.js │ │ ├── HomePagePreview.js │ │ ├── MeetupPreview.js │ │ ├── NavbarPreview.js │ │ └── PastMeetupsPagePreview.js ├── components │ ├── Content.js │ ├── CustomLink.js │ ├── Footer │ │ ├── Footer.js │ │ ├── index.js │ │ └── styles.scss │ ├── Layout.js │ ├── Map.js │ └── Navbar │ │ ├── Navbar.js │ │ ├── index.js │ │ └── styles.scss ├── img │ ├── favicon.png │ ├── headshot-placeholder.svg │ └── location.svg ├── pages │ ├── 404.js │ ├── about │ │ └── index.md │ ├── footer │ │ └── index.md │ ├── home │ │ └── index.md │ ├── index.js │ ├── meetups │ │ ├── august-2018.md │ │ ├── july-2018.md │ │ ├── november-2018-meetup.md │ │ ├── october-2018.md │ │ └── september-2018.md │ ├── navbar │ │ └── index.md │ └── pastMeetups │ │ └── index.md ├── styles │ ├── 404.scss │ ├── about-page.scss │ ├── generic.scss │ ├── home.scss │ ├── index.js │ ├── meetup.scss │ ├── mixins.scss │ ├── past-meetups-page.scss │ ├── reset.scss │ └── variables.scss └── templates │ ├── about-page.js │ ├── meetup.js │ └── past-meetups-page.js ├── static ├── admin │ └── config.yml └── img │ ├── annie-spratt-608001-unsplash.jpg │ ├── benjamin-parker-736167-unsplash.jpg │ ├── edward-cisneros-415601-unsplash.jpg │ ├── email.svg │ ├── facebook.svg │ ├── humphrey-muleba-795250-unsplash.jpg │ ├── jakob-dalbjorn-730178-unsplash.jpg │ ├── jonas-kakaroto-577554-unsplash.jpg │ ├── js-wakanda.png │ ├── logo.svg │ ├── lucas-sankey-378674-unsplash.jpg │ ├── marius-ciocirlan-398931-unsplash.jpg │ ├── meetup.svg │ ├── michael-dam-258165-unsplash.jpg │ ├── neonbrand-509131-unsplash.jpg │ ├── organizer-1.jpg │ ├── organizer-2.jpg │ ├── ramy-kabalan-796973-unsplash.jpg │ ├── teemu-paananen-376238-unsplash.jpg │ ├── television.png │ └── twitter.svg └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Project dependencies 2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 3 | node_modules 4 | .cache/ 5 | # Build directory 6 | public/ 7 | static/admin/*.bundle.* 8 | .DS_Store 9 | yarn-error.log 10 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v9.10.0 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 gatsbyjs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gatsby & Netlify CMS Example 2 | 3 | An example website built using Gatsby V2 and Netlify CMS. The website is a fake JavaScript meetup site that lists upcoming meetups, information about the meetup group, as well as a list of past meetups. 4 | 5 | The purpose of the repository is to provide an idea of how a Gatsby project is structured with Netlify CMS. You can easily deploy your own instance of this application by clicking the button below: 6 | 7 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/robertcoopercode/gatsby-netlify-cms) 8 | 9 | ## Local Development 10 | 11 | ### Prerequisites 12 | 13 | - Node (see [.nvmrc](./.nvmrc) for version) 14 | 15 | ### Run the project 16 | 17 | ``` 18 | $ git clone git@github.com:robertcoopercode/gatsby-netlify-cms.git 19 | $ cd gatsby-netlify-cms 20 | $ yarn 21 | $ yarn develop 22 | ``` 23 | 24 | To test the CMS locally, you'll to need run a production build of the site: 25 | 26 | ``` 27 | $ yarn build 28 | $ yarn serve 29 | ``` 30 | 31 | ### Setting up the CMS 32 | 33 | For details on how to configure the CMS, take a look at the [Netlify CMS Docs](https://www.netlifycms.org/docs/intro). 34 | 35 | ## Useful Resources 36 | - ["Official" Gatsby and Netlify CMS starter](https://github.com/netlify-templates/gatsby-starter-netlify-cms) 37 | This starter includes a blog built with Gatsby and Netlify CMS. It was actually used as the starting off point for this repository. 38 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: "Gatsby + Netlify CMS Starter", 4 | }, 5 | plugins: [ 6 | "gatsby-plugin-react-helmet", 7 | "gatsby-plugin-sass", 8 | { 9 | resolve: "gatsby-source-filesystem", 10 | options: { 11 | path: `${__dirname}/src/pages`, 12 | name: "pages", 13 | }, 14 | }, 15 | { 16 | resolve: "gatsby-source-filesystem", 17 | options: { 18 | path: `${__dirname}/src/img`, 19 | name: "images", 20 | }, 21 | }, 22 | "gatsby-plugin-sharp", 23 | "gatsby-transformer-sharp", 24 | { 25 | resolve: "gatsby-transformer-remark", 26 | options: { 27 | plugins: [], 28 | }, 29 | }, 30 | { 31 | resolve: "gatsby-plugin-netlify-cms", 32 | options: { 33 | modulePath: `${__dirname}/src/cms/cms.js`, 34 | }, 35 | }, 36 | { 37 | resolve: `gatsby-plugin-favicon`, 38 | options: { 39 | logo: "./src/img/favicon.png", 40 | }, 41 | }, 42 | "gatsby-plugin-netlify", // make sure to keep it last in the array 43 | ], 44 | }; 45 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | const path = require("path"); 3 | const { createFilePath } = require("gatsby-source-filesystem"); 4 | 5 | exports.createPages = ({ actions, graphql }) => { 6 | const { createPage } = actions; 7 | 8 | return graphql(` 9 | { 10 | allMarkdownRemark(limit: 1000) { 11 | edges { 12 | node { 13 | id 14 | fields { 15 | slug 16 | } 17 | frontmatter { 18 | path 19 | templateKey 20 | } 21 | } 22 | } 23 | } 24 | } 25 | `).then(result => { 26 | if (result.errors) { 27 | result.errors.forEach(e => console.error(e.toString())); 28 | return Promise.reject(result.errors); 29 | } 30 | 31 | // Filter out the footer, navbar, and meetups so we don't create pages for those 32 | const postOrPage = result.data.allMarkdownRemark.edges.filter(edge => { 33 | if (edge.node.frontmatter.templateKey === "navbar") { 34 | return false; 35 | } else if (edge.node.frontmatter.templateKey === "footer") { 36 | return false; 37 | } else { 38 | return !Boolean(edge.node.fields.slug.match(/^\/meetups\/.*$/)); 39 | } 40 | }); 41 | 42 | postOrPage.forEach(edge => { 43 | let component, pathName; 44 | if (edge.node.frontmatter.templateKey === "home-page") { 45 | pathName = "/"; 46 | component = path.resolve(`src/pages/index.js`); 47 | } else { 48 | pathName = edge.node.frontmatter.path || edge.node.fields.slug; 49 | component = path.resolve(`src/templates/${String(edge.node.frontmatter.templateKey)}.js`); 50 | } 51 | const id = edge.node.id; 52 | createPage({ 53 | path: pathName, 54 | component, 55 | // additional data can be passed via context 56 | context: { 57 | id, 58 | }, 59 | }); 60 | }); 61 | }); 62 | }; 63 | 64 | exports.onCreateNode = ({ node, actions, getNode }) => { 65 | const { createNodeField } = actions; 66 | 67 | if (node.internal.type === `MarkdownRemark`) { 68 | const value = createFilePath({ node, getNode }); 69 | createNodeField({ 70 | name: `slug`, 71 | node, 72 | value, 73 | }); 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "public" 3 | command = "npm run build" 4 | [build.environment] 5 | YARN_VERSION = "1.3.2" 6 | YARN_FLAGS = "--no-ignore-optional" 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-starter-netlify-cms", 3 | "description": "Example Gatsby, and Netlify CMS project", 4 | "version": "1.1.3", 5 | "author": "Robert Cooper", 6 | "dependencies": { 7 | "date-fns": "^1.29.0", 8 | "gatsby": "^2.0.0", 9 | "gatsby-plugin-favicon": "^3.1.4", 10 | "gatsby-plugin-netlify": "^2.0.0", 11 | "gatsby-plugin-netlify-cms": "^3.0.0", 12 | "gatsby-plugin-react-helmet": "^3.0.0", 13 | "gatsby-plugin-sass": "^2.0.1", 14 | "gatsby-plugin-sharp": "^2.0.5", 15 | "gatsby-remark-images": "^2.0.1", 16 | "gatsby-source-filesystem": "^2.0.1", 17 | "gatsby-transformer-remark": "^2.1.1", 18 | "gatsby-transformer-sharp": "^2.1.1", 19 | "lodash": "^4.17.5", 20 | "lodash-webpack-plugin": "^0.11.4", 21 | "netlify-cms": "^2.1.1", 22 | "node-sass": "^4.9.2", 23 | "parcel-bundler": "^1.9.4", 24 | "prop-types": "^15.6.0", 25 | "react": "^16.2.0", 26 | "react-dom": "^16.4.1", 27 | "react-google-maps": "^9.4.5", 28 | "react-helmet": "^5.2.0", 29 | "react-markdown": "^4.0.3", 30 | "uuid": "^3.2.1" 31 | }, 32 | "keywords": [ 33 | "gatsby" 34 | ], 35 | "license": "MIT", 36 | "main": "n/a", 37 | "scripts": { 38 | "start": "npm run develop", 39 | "clean": "rimraf .cache public", 40 | "build": "npm run clean && gatsby build", 41 | "develop": "npm run clean && gatsby develop", 42 | "serve": "gatsby serve", 43 | "format": "prettier --trailing-comma es5 --no-semi --single-quote --write \"{gatsby-*.js,src/**/*.js}\"", 44 | "test": "echo \"Error: no test specified\" && exit 1" 45 | }, 46 | "devDependencies": { 47 | "prettier": "^1.7.4", 48 | "rimraf": "^2.6.2" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "rangeStrategy": "replace", 6 | "lockFileMaintenance": { 7 | "enabled": true, 8 | "extends": "schedule:weekly" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/cms/cms.js: -------------------------------------------------------------------------------- 1 | import CMS from "netlify-cms"; 2 | 3 | import AboutPagePreview from "./preview-templates/AboutPagePreview"; 4 | import HomePagePreview from "./preview-templates/HomePagePreview"; 5 | import MeetupPreview from "./preview-templates/MeetupPreview"; 6 | import FooterPreview from "./preview-templates/FooterPreview"; 7 | import NavbarPreview from "./preview-templates/NavbarPreview"; 8 | import PastMeetupsPagePreview from "./preview-templates/PastMeetupsPagePreview"; 9 | 10 | CMS.registerPreviewTemplate("meetups", MeetupPreview); 11 | CMS.registerPreviewTemplate("footer", FooterPreview); 12 | CMS.registerPreviewTemplate("navbar", NavbarPreview); 13 | CMS.registerPreviewTemplate("about", AboutPagePreview); 14 | CMS.registerPreviewTemplate("home", HomePagePreview); 15 | CMS.registerPreviewTemplate("pastMeetups", PastMeetupsPagePreview); 16 | -------------------------------------------------------------------------------- /src/cms/preview-templates/AboutPagePreview.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { AboutPageTemplate } from "../../templates/about-page"; 4 | 5 | const AboutPagePreview = ({ entry, widgetFor }) => ( 6 | 13 | ); 14 | 15 | AboutPagePreview.propTypes = { 16 | entry: PropTypes.shape({ 17 | getIn: PropTypes.func, 18 | }), 19 | widgetFor: PropTypes.func, 20 | }; 21 | 22 | export default AboutPagePreview; 23 | -------------------------------------------------------------------------------- /src/cms/preview-templates/FooterPreview.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import { FooterTemplate } from "../../components/Footer"; 5 | 6 | const FooterPreview = ({ entry }) => { 7 | const data = entry.getIn(["data"]).toJS(); 8 | return ; 9 | }; 10 | 11 | FooterPreview.propTypes = { 12 | entry: PropTypes.shape({ 13 | getIn: PropTypes.func, 14 | }), 15 | }; 16 | 17 | export default FooterPreview; 18 | -------------------------------------------------------------------------------- /src/cms/preview-templates/HomePagePreview.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import { HomePageTemplate } from "../../pages/index"; 5 | 6 | const HomePagePreview = ({ entry }) => { 7 | const home = entry.getIn(["data"]).toJS(); 8 | return ; 9 | }; 10 | 11 | HomePagePreview.propTypes = { 12 | entry: PropTypes.shape({ 13 | getIn: PropTypes.func, 14 | }), 15 | }; 16 | 17 | export default HomePagePreview; 18 | -------------------------------------------------------------------------------- /src/cms/preview-templates/MeetupPreview.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import format from "date-fns/format"; 4 | 5 | import MeetupTemplate from "../../templates/meetup"; 6 | 7 | const MeetupPreview = ({ entry }) => { 8 | const meetup = entry.getIn(["data"]).toJS(); 9 | const rawDate = meetup.date; 10 | const formattedDate = format(rawDate, "MMMM Do YYYY @ h:mm A"); 11 | return ; 12 | }; 13 | 14 | MeetupPreview.propTypes = { 15 | entry: PropTypes.shape({ 16 | getIn: PropTypes.func, 17 | }), 18 | }; 19 | 20 | export default MeetupPreview; 21 | -------------------------------------------------------------------------------- /src/cms/preview-templates/NavbarPreview.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import { NavbarTemplate } from "../../components/Navbar"; 5 | 6 | const NavbarPreview = ({ entry }) => { 7 | const data = entry.getIn(["data"]).toJS(); 8 | return ; 9 | }; 10 | 11 | NavbarPreview.propTypes = { 12 | entry: PropTypes.shape({ 13 | getIn: PropTypes.func, 14 | }), 15 | }; 16 | 17 | export default NavbarPreview; 18 | -------------------------------------------------------------------------------- /src/cms/preview-templates/PastMeetupsPagePreview.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { PastMeetupsPageTemplate } from "../../templates/past-meetups-page"; 4 | 5 | const PastMeetupsPagePreview = ({ entry, widgetFor }) => { 6 | return ( 7 | 12 | ); 13 | }; 14 | 15 | PastMeetupsPagePreview.propTypes = { 16 | entry: PropTypes.shape({ 17 | getIn: PropTypes.func, 18 | }), 19 | widgetFor: PropTypes.func, 20 | }; 21 | 22 | export default PastMeetupsPagePreview; 23 | -------------------------------------------------------------------------------- /src/components/Content.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | export const HTMLContent = ({ content, className }) => ( 5 |
6 | ); 7 | 8 | HTMLContent.propTypes = { 9 | content: PropTypes.node, 10 | className: PropTypes.string, 11 | }; 12 | 13 | export default HTMLContent; 14 | -------------------------------------------------------------------------------- /src/components/CustomLink.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Link } from "gatsby"; 4 | 5 | export const CustomLink = ({ linkType, linkURL, children, className = "" }) => { 6 | if (linkType === "internal") { 7 | return ( 8 | 9 | {children} 10 | 11 | ); 12 | } else { 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | }; 20 | 21 | CustomLink.propTypes = { 22 | linkType: PropTypes.string, 23 | }; 24 | 25 | export default CustomLink; 26 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./styles.scss"; 3 | 4 | export const FooterTemplate = ({ data }) => { 5 | const { logoImage, socialLinks } = data; 6 | 7 | return ( 8 | 52 | ); 53 | }; 54 | 55 | const Footer = props => { 56 | if (!props.data) { 57 | return null; 58 | } 59 | const data = props.data.edges[0].node.frontmatter; 60 | return ; 61 | }; 62 | 63 | export { Footer }; 64 | -------------------------------------------------------------------------------- /src/components/Footer/index.js: -------------------------------------------------------------------------------- 1 | export * from "./Footer"; 2 | -------------------------------------------------------------------------------- /src/components/Footer/styles.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/variables"; 2 | @import "../../styles/mixins"; 3 | 4 | .footer { 5 | background-color: $background-dark; 6 | @include medium-font; 7 | color: $body-light; 8 | padding: 80px 20px; 9 | 10 | @include respond-to("small") { 11 | padding: 40px 20px; 12 | height: $footer-height; 13 | } 14 | 15 | &-container { 16 | display: flex; 17 | flex-direction: column; 18 | flex-wrap: wrap; 19 | height: 100%; 20 | 21 | @include respond-to("small") { 22 | flex-direction: row; 23 | } 24 | } 25 | 26 | &-top { 27 | display: flex; 28 | flex-direction: column; 29 | flex-grow: 1; 30 | align-items: center; 31 | width: 100%; 32 | 33 | @include respond-to("small") { 34 | flex-direction: row; 35 | } 36 | } 37 | 38 | &-about { 39 | text-align: center; 40 | 41 | @include respond-to("small") { 42 | text-align: left; 43 | } 44 | } 45 | 46 | &-aboutTitleImg { 47 | max-height: 60px; 48 | height: auto; 49 | margin-bottom: 10px; 50 | } 51 | 52 | &-aboutDescription { 53 | @include medium-font(); 54 | } 55 | 56 | &-socialMenu { 57 | margin: 20px; 58 | 59 | @include respond-to("small") { 60 | margin-left: auto; 61 | } 62 | } 63 | 64 | &-socialMenuItem { 65 | padding: 15px 0; 66 | } 67 | 68 | &-socialLink { 69 | @include medium-font; 70 | font-weight: 400; 71 | text-decoration: none; 72 | color: $body-light; 73 | display: flex; 74 | align-items: center; 75 | } 76 | 77 | &-socialLinkIcon { 78 | width: 40px; 79 | margin-right: 20px; 80 | } 81 | 82 | &-flag { 83 | width: 100%; 84 | display: flex; 85 | justify-content: center; 86 | font-size: 2rem; 87 | padding-top: 20px; 88 | 89 | @include respond-to("small") { 90 | margin-left: auto; 91 | } 92 | } 93 | 94 | &-bottom { 95 | margin-top: auto; 96 | width: 100%; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/components/Layout.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Helmet from "react-helmet"; 3 | import { graphql } from "gatsby"; 4 | 5 | import "../styles"; 6 | import { Navbar } from "../components/Navbar"; 7 | import { Footer } from "../components/Footer"; 8 | 9 | const TemplateWrapper = ({ footerData = null, navbarData = null, children }) => ( 10 |
11 | 12 | 13 | 14 | 15 | 16 |
{children}
17 |
18 |
19 | ); 20 | 21 | export const query = graphql` 22 | fragment LayoutFragment on Query { 23 | footerData: allMarkdownRemark(filter: { frontmatter: { templateKey: { eq: "footer" } } }) { 24 | edges { 25 | node { 26 | id 27 | frontmatter { 28 | logoImage { 29 | image 30 | imageAlt 31 | tagline 32 | } 33 | socialLinks { 34 | image 35 | imageAlt 36 | label 37 | linkURL 38 | } 39 | } 40 | } 41 | } 42 | } 43 | navbarData: allMarkdownRemark(filter: { frontmatter: { templateKey: { eq: "navbar" } } }) { 44 | edges { 45 | node { 46 | id 47 | frontmatter { 48 | logoImage { 49 | image 50 | imageAlt 51 | } 52 | menuItems { 53 | label 54 | linkType 55 | linkURL 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } 62 | `; 63 | 64 | export default TemplateWrapper; 65 | -------------------------------------------------------------------------------- /src/components/Map.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { withScriptjs, withGoogleMap, GoogleMap, Marker } from "react-google-maps"; 4 | import LocationIcon from "../img/location.svg"; 5 | 6 | class Map extends Component { 7 | render() { 8 | if (!this.props.latitude || !this.props.longitude || !this.props.link) { 9 | return null; 10 | } 11 | return ( 12 | 17 | window.open(this.props.link)} 21 | /> 22 | 23 | ); 24 | } 25 | } 26 | 27 | Map.propTypes = { 28 | link: PropTypes.string, 29 | latitude: PropTypes.number, 30 | longitude: PropTypes.number, 31 | }; 32 | 33 | export default withScriptjs(withGoogleMap(Map)); 34 | 35 | const exampleMapStyles = [ 36 | { 37 | featureType: "all", 38 | elementType: "labels.text.fill", 39 | stylers: [ 40 | { 41 | color: "#ffffff", 42 | }, 43 | ], 44 | }, 45 | { 46 | featureType: "all", 47 | elementType: "labels.text.stroke", 48 | stylers: [ 49 | { 50 | color: "#000000", 51 | }, 52 | { 53 | lightness: 13, 54 | }, 55 | ], 56 | }, 57 | { 58 | featureType: "administrative", 59 | elementType: "geometry.fill", 60 | stylers: [ 61 | { 62 | color: "#000000", 63 | }, 64 | ], 65 | }, 66 | { 67 | featureType: "administrative", 68 | elementType: "geometry.stroke", 69 | stylers: [ 70 | { 71 | color: "#144b53", 72 | }, 73 | { 74 | lightness: 14, 75 | }, 76 | { 77 | weight: 1.4, 78 | }, 79 | ], 80 | }, 81 | { 82 | featureType: "landscape", 83 | elementType: "all", 84 | stylers: [ 85 | { 86 | color: "#08304b", 87 | }, 88 | ], 89 | }, 90 | { 91 | featureType: "poi", 92 | elementType: "geometry", 93 | stylers: [ 94 | { 95 | color: "#0c4152", 96 | }, 97 | { 98 | lightness: 5, 99 | }, 100 | ], 101 | }, 102 | { 103 | featureType: "road.highway", 104 | elementType: "geometry.fill", 105 | stylers: [ 106 | { 107 | color: "#000000", 108 | }, 109 | ], 110 | }, 111 | { 112 | featureType: "road.highway", 113 | elementType: "geometry.stroke", 114 | stylers: [ 115 | { 116 | color: "#0b434f", 117 | }, 118 | { 119 | lightness: 25, 120 | }, 121 | ], 122 | }, 123 | { 124 | featureType: "road.arterial", 125 | elementType: "geometry.fill", 126 | stylers: [ 127 | { 128 | color: "#000000", 129 | }, 130 | ], 131 | }, 132 | { 133 | featureType: "road.arterial", 134 | elementType: "geometry.stroke", 135 | stylers: [ 136 | { 137 | color: "#0b3d51", 138 | }, 139 | { 140 | lightness: 16, 141 | }, 142 | ], 143 | }, 144 | { 145 | featureType: "road.local", 146 | elementType: "geometry", 147 | stylers: [ 148 | { 149 | color: "#000000", 150 | }, 151 | ], 152 | }, 153 | { 154 | featureType: "transit", 155 | elementType: "all", 156 | stylers: [ 157 | { 158 | color: "#146474", 159 | }, 160 | ], 161 | }, 162 | { 163 | featureType: "water", 164 | elementType: "all", 165 | stylers: [ 166 | { 167 | color: "#021019", 168 | }, 169 | ], 170 | }, 171 | ]; 172 | -------------------------------------------------------------------------------- /src/components/Navbar/Navbar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "./styles.scss"; 4 | import CustomLink from "../CustomLink"; 5 | 6 | export const NavbarTemplate = ({ data }) => ( 7 | 26 | ); 27 | 28 | const Navbar = props => { 29 | if (!props.data) { 30 | return null; 31 | } 32 | const data = props.data.edges[0].node.frontmatter; 33 | return ; 34 | }; 35 | 36 | export { Navbar }; 37 | -------------------------------------------------------------------------------- /src/components/Navbar/index.js: -------------------------------------------------------------------------------- 1 | export * from "./Navbar"; 2 | -------------------------------------------------------------------------------- /src/components/Navbar/styles.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/variables.scss"; 2 | 3 | .navbar { 4 | background-color: $background-dark; 5 | height: $navbar-height; 6 | 7 | &-container { 8 | display: flex; 9 | height: 100%; 10 | } 11 | 12 | &-menu { 13 | display: flex; 14 | flex-grow: 0; 15 | margin-left: auto; 16 | margin-right: -1rem; 17 | } 18 | 19 | &-menuItem { 20 | display: flex; 21 | flex-direction: column; 22 | justify-content: center; 23 | } 24 | 25 | &-menuLink { 26 | color: $menu-inactive; 27 | padding: 0 0.5rem; 28 | margin: 0 0.5rem; 29 | text-decoration: none; 30 | font-weight: 300; 31 | letter-spacing: 0.4px; 32 | transition: all $transition-duration ease-in-out; 33 | 34 | &:hover, 35 | &--active { 36 | color: $menu-active; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/src/img/favicon.png -------------------------------------------------------------------------------- /src/img/headshot-placeholder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/img/location.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Layout from "../components/Layout"; 3 | import Helmet from "react-helmet"; 4 | import "../styles/404.scss"; 5 | 6 | const NotFoundPage = () => ( 7 | 8 | 9 | Page Not Found 10 | 11 |
12 |

NOT FOUND

13 |

{` 14 | (___________________________()6 \`-, 15 | ( ______________________ /''"\` 16 | //\\ //\\ 17 | "" "" "" "" 18 | `}

19 |
20 |
21 | ); 22 | 23 | export default NotFoundPage; 24 | -------------------------------------------------------------------------------- /src/pages/about/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | templateKey: about-page 3 | title: Wakanda JavaScript Developer Meetup 4 | mainImage: 5 | image: /img/teemu-paananen-376238-unsplash.jpg 6 | imageAlt: Wakanda JavaScript developer presenting at a meetup. 7 | gallery: 8 | - image: /img/neonbrand-509131-unsplash.jpg 9 | imageAlt: Wakanda JavaScript developer presenting at a meetup. 10 | - image: /img/jakob-dalbjorn-730178-unsplash.jpg 11 | imageAlt: Wakanda JavaScript developer presenting at a meetup. 12 | - image: /img/annie-spratt-608001-unsplash.jpg 13 | imageAlt: Wakanda developers working together at a table. 14 | developerGroups: |- 15 | ## Other Wakanda Developer Groups 16 | 17 | * [React & React Native Meetup](https://www.google.com) 18 | * [Python Meetup](https://www.google.com) 19 | * [Google Developer Meetup](https://www.google.com) 20 | * [Vue.js Meetup](https://www.google.com) 21 | * [Ruby Meetup](https://www.google.com) 22 | organizers: 23 | title: Group Organizers 24 | gallery: 25 | - image: /img/organizer-1.jpg 26 | imageAlt: Tom Cruise 27 | name: Tom Cruise 28 | - image: /img/organizer-2.jpg 29 | imageAlt: Tom Hanks 30 | name: Tom Hanks 31 | seo: 32 | browserTitle: About | JavaScript Wakanda 33 | description: >- 34 | JavaScript Wakanda is a meetup group that holds monthly meetups where 35 | JavaScript developers get together for presentations and to meet others in 36 | the community. 37 | title: About | JS Wakanda 38 | --- 39 | ## Sharing Ideas and Meeting Others 40 | 41 | JS Wakanda is a user group run by volunteers. We meet in borrowed spaces, graciously provided by different companies and organisations. Our purpose is to provide a place where our members have a good time talking about programming, sharing their knowledge and meeting other passionate folks. We keep it friendly, inclusive and positive. 42 | -------------------------------------------------------------------------------- /src/pages/footer/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | templateKey: footer 3 | logoImage: 4 | image: /img/js-wakanda.png 5 | imageAlt: JavaScript Wakanda 6 | tagline: Your friendly local Wakanda meetup 7 | socialLinks: 8 | - image: /img/meetup.svg 9 | imageAlt: Join us on meetup.com 10 | label: meetup.com 11 | linkURL: 'https://www.meetup.com/' 12 | - image: /img/twitter.svg 13 | imageAlt: Follow us on Twitter 14 | label: twitter.com 15 | linkURL: 'https://twitter.com/' 16 | - image: /img/facebook.svg 17 | imageAlt: Join our Facebook group 18 | label: facebook.com 19 | linkURL: 'https://www.facebook.com/' 20 | - image: /img/email.svg 21 | imageAlt: Contact us by email 22 | label: email us 23 | linkURL: 'mailto:contact@js-wakanda.org' 24 | --- 25 | 26 | -------------------------------------------------------------------------------- /src/pages/home/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | templateKey: home-page 3 | headerImage: 4 | image: /img/television.png 5 | imageAlt: JavaScript Wakanda 6 | title: Meet other JavaScript Developers in Wakanda 7 | upcomingMeetupHeading: Upcoming Meetup 8 | noUpcomingMeetupText: Details to be announced. 9 | mapsNote: Clicking the pin opens Google Maps in a new tab. 10 | callToActions: 11 | firstCTA: 12 | heading: Past Meetups 13 | linkType: internal 14 | linkURL: /meetups 15 | subHeading: Look at what topics were presented at past meetups. 16 | secondCTA: 17 | heading: Volunteer to Present 18 | linkType: external 19 | linkURL: 'mailto:contact@js-wakanda.org' 20 | subHeading: Want to present at an upcoming meetup? Contact us. 21 | seo: 22 | browserTitle: JS Wakanda 23 | description: >- 24 | JavaScript meetup group in Wakanda where JavaScript developers get together 25 | for presentations and to meet others in the community. 26 | title: JavaScript Wakanda 27 | --- 28 | 29 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { graphql } from "gatsby"; 4 | import Helmet from "react-helmet"; 5 | import isAfter from "date-fns/is_after"; 6 | 7 | import Layout from "../components/Layout"; 8 | import Map from "../components/Map"; 9 | import HeadshotPlaceholder from "../img/headshot-placeholder.svg"; 10 | import CustomLink from "../components/CustomLink"; 11 | import "../styles/home.scss"; 12 | 13 | export const HomePageTemplate = ({ home, upcomingMeetup = null }) => { 14 | const presenters = upcomingMeetup && upcomingMeetup.presenters; 15 | const latitude = upcomingMeetup && parseFloat(upcomingMeetup.location.mapsLatitude); 16 | const longitude = upcomingMeetup && parseFloat(upcomingMeetup.location.mapsLongitude); 17 | return ( 18 | <> 19 |
20 |
21 | {home.headerImage && {home.headerImage.imageAlt}} 22 |

23 | {home.title} 24 |

25 |
26 |
27 |
28 |
29 |

{home.upcomingMeetupHeading}

30 | {upcomingMeetup ? ( 31 | <> 32 |

33 | Date: 34 | {upcomingMeetup.formattedDate} 35 |

36 |

37 | Location: 38 | {upcomingMeetup.location.name} 39 |

40 | {presenters.length > 0 && ( 41 |
42 | {presenters.map(presenter => ( 43 |
44 | {presenter.image 49 | {presenter.name} 50 | 51 | {presenter.presentationTitle} 52 | 53 |

{presenter.text}

54 |
55 | ))} 56 |
57 | )} 58 |

{home.mapsNote}

59 |
60 | } 64 | containerElement={
} 65 | mapElement={
} 66 | link={upcomingMeetup.location.mapsLink} 67 | latitude={latitude} 68 | longitude={longitude} 69 | /> 70 |
71 | 72 | ) : ( 73 |

{home.noUpcomingMeetupText}

74 | )} 75 |
76 |
77 |
78 | 83 |
84 | {home.callToActions.firstCTA.heading} 85 |

{home.callToActions.firstCTA.subHeading}

86 |
87 |
88 | 93 |
94 | {home.callToActions.secondCTA.heading} 95 |

{home.callToActions.secondCTA.subHeading}

96 |
97 |
98 |
99 | 100 | ); 101 | }; 102 | 103 | class HomePage extends React.Component { 104 | render() { 105 | const { data } = this.props; 106 | const { 107 | data: { footerData, navbarData }, 108 | } = this.props; 109 | const { frontmatter: home } = data.homePageData.edges[0].node; 110 | const { 111 | seo: { title: seoTitle, description: seoDescription, browserTitle }, 112 | } = home; 113 | let upcomingMeetup = null; 114 | // Find the next meetup that is closest to today 115 | data.allMarkdownRemark.edges.every(item => { 116 | const { frontmatter: meetup } = item.node; 117 | if (isAfter(meetup.rawDate, new Date())) { 118 | upcomingMeetup = meetup; 119 | return true; 120 | } else { 121 | return false; 122 | } 123 | }); 124 | return ( 125 | 126 | 127 | 128 | 129 | {browserTitle} 130 | 131 | 132 | 133 | ); 134 | } 135 | } 136 | 137 | HomePage.propTypes = { 138 | data: PropTypes.shape({ 139 | allMarkdownRemark: PropTypes.shape({ 140 | edges: PropTypes.array, 141 | }), 142 | }), 143 | }; 144 | 145 | export default HomePage; 146 | 147 | export const pageQuery = graphql` 148 | query HomePageQuery { 149 | allMarkdownRemark( 150 | filter: { frontmatter: { presenters: { elemMatch: { text: { ne: null } } } } } 151 | sort: { order: DESC, fields: frontmatter___date } 152 | ) { 153 | edges { 154 | node { 155 | frontmatter { 156 | title 157 | formattedDate: date(formatString: "MMMM Do YYYY @ h:mm A") 158 | rawDate: date 159 | presenters { 160 | name 161 | image 162 | text 163 | presentationTitle 164 | } 165 | location { 166 | mapsLatitude 167 | mapsLongitude 168 | mapsLink 169 | name 170 | } 171 | } 172 | } 173 | } 174 | } 175 | ...LayoutFragment 176 | homePageData: allMarkdownRemark(filter: { frontmatter: { templateKey: { eq: "home-page" } } }) { 177 | edges { 178 | node { 179 | frontmatter { 180 | title 181 | headerImage { 182 | image 183 | imageAlt 184 | } 185 | upcomingMeetupHeading 186 | noUpcomingMeetupText 187 | mapsNote 188 | callToActions { 189 | firstCTA { 190 | heading 191 | subHeading 192 | linkType 193 | linkURL 194 | } 195 | secondCTA { 196 | heading 197 | subHeading 198 | linkType 199 | linkURL 200 | } 201 | } 202 | seo { 203 | browserTitle 204 | title 205 | description 206 | } 207 | } 208 | } 209 | } 210 | } 211 | } 212 | `; 213 | -------------------------------------------------------------------------------- /src/pages/meetups/august-2018.md: -------------------------------------------------------------------------------- 1 | --- 2 | templateKey: meetup 3 | title: August 2018 4 | date: 2018-08-14T23:00:00.000Z 5 | presenters: 6 | - image: /img/ramy-kabalan-796973-unsplash.jpg 7 | name: Fred Champ 8 | presentationTitle: Network optimizations 101 9 | text: >+ 10 | If you have an application that requires network requests to run, you 11 | might want to come watch this presentation. We'll cover congestion 12 | control, caching, batching and a couple advanced strategies to make your 13 | application faster all while saving on infrastructure costs. 14 | 15 | - image: /img/marius-ciocirlan-398931-unsplash.jpg 16 | name: Ben Wilson 17 | presentationTitle: Memoizing functions 18 | text: >- 19 | Ben will tell us all about the power of functional programming, 20 | specifically memoization and how it can help speed up frequently used side 21 | effect-less functions. 22 | location: 23 | mapsLatitude: 64.843779 24 | mapsLink: 'https://goo.gl/maps/LLgicn3zGuy' 25 | mapsLongitude: -147.718189 26 | name: Fairbanks Ice Museum 27 | --- 28 | 29 | -------------------------------------------------------------------------------- /src/pages/meetups/july-2018.md: -------------------------------------------------------------------------------- 1 | --- 2 | templateKey: meetup 3 | title: July 2018 4 | date: 2018-07-10T23:00:00.000Z 5 | presenters: 6 | - image: /img/lucas-sankey-378674-unsplash.jpg 7 | links: 8 | - linkText: Iterators and Generators 9 | linkURL: 'https://google.com/' 10 | - linkText: Async Proposal 11 | linkURL: 'https://google.com/' 12 | - linkText: Babel Transform for async generators 13 | linkURL: 'https://google.com/' 14 | - linkText: TC39 Spec for the feature. 15 | linkURL: 'https://google.com/' 16 | name: Steve Wozniak 17 | presentationTitle: Async Generators - Looking at the ES2018 Spec 18 | text: >- 19 | Building on Jason's talk from last meet-up, we'll look at Async 20 | Generators, a widely discussed feature documented in the new ES2018 spec. 21 | We'll look at some use cases and live examples. 22 | location: 23 | mapsLatitude: 64.843779 24 | mapsLink: 'https://goo.gl/maps/Rm6ihxVrZGK2' 25 | mapsLongitude: -147 26 | name: Fairbanks Ice Museum 27 | --- 28 | 29 | -------------------------------------------------------------------------------- /src/pages/meetups/november-2018-meetup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: November 2060 3 | date: 2060-11-09T23:00:00.000Z 4 | presenters: 5 | - image: /img/jonas-kakaroto-577554-unsplash.jpg 6 | links: 7 | - linkText: Github Example 8 | linkURL: 'https://github.com/' 9 | - linkText: Conference Talk Recording 10 | linkURL: 'https://github.com/' 11 | name: Wes Tanner 12 | presentationTitle: Advanced React 13 | text: >- 14 | Wes is going to guide use on how to build a full stack react and graphql 15 | app using Appolo, GraphQL Yoga, and Prisma. 16 | - image: /img/benjamin-parker-736167-unsplash.jpg 17 | links: 18 | - linkText: Twitter 19 | linkURL: 'https://twitter.com/' 20 | name: Scott Brolinski 21 | presentationTitle: Meteor.js Framework 22 | text: >- 23 | Scott will take use through the Meteor JavaScript framework and how it’s 24 | awesome in every way. 25 | location: 26 | mapsLatitude: 64.843779 27 | mapsLink: 'https://goo.gl/maps/Rm6ihxVrZGK2' 28 | mapsLongitude: -147.718189 29 | name: Fairbanks Ice Museum 30 | --- 31 | 32 | -------------------------------------------------------------------------------- /src/pages/meetups/october-2018.md: -------------------------------------------------------------------------------- 1 | --- 2 | templateKey: meetup 3 | title: October 2018 4 | date: 2018-10-09T23:00:00.000Z 5 | presenters: 6 | - image: /img/michael-dam-258165-unsplash.jpg 7 | links: 8 | - linkText: Authenticated Systems Workshop (Github) 9 | linkURL: 'https://google.com/' 10 | name: Janet Morisette 11 | presentationTitle: Authenticated Systems Workshop 12 | text: >- 13 | In this talk we will go over a few ways you can implement authentication 14 | (including offline authentication strategies) and then dive into some code 15 | for both a server and a client which you can adapt to suit your own app. 16 | - image: /img/humphrey-muleba-795250-unsplash.jpg 17 | links: 18 | - linkText: Gatsby Repo 19 | linkURL: 'https://google.com/' 20 | name: Sarah Jones 21 | presentationTitle: Gatsby! 22 | text: >- 23 | Get to know more about the basics of using Gatsby with Sarah. She is going 24 | to use Nike's website as an example. 25 | location: 26 | mapsLatitude: 64.843779 27 | mapsLink: 'https://goo.gl/maps/Rm6ihxVrZGK2' 28 | mapsLongitude: -147.718189 29 | name: Fairbanks Ice Museum 30 | --- 31 | 32 | -------------------------------------------------------------------------------- /src/pages/meetups/september-2018.md: -------------------------------------------------------------------------------- 1 | --- 2 | templateKey: meetup 3 | title: September 2018 4 | date: 2018-09-11T22:00:00.000Z 5 | presenters: 6 | - image: /img/edward-cisneros-415601-unsplash.jpg 7 | links: 8 | - linkText: Async/Await 9 | linkURL: 'https://google.com/' 10 | - linkText: Promises 11 | linkURL: 'https://google.com/' 12 | name: Brittany Vonn 13 | presentationTitle: 'No sugar please: a tearing down of abstractions' 14 | text: >- 15 | To demistify one of JS' hotest bits of syntax sugar and the familiar 16 | Promise, Brittany will break down the mechanics of both async patterns by 17 | building his own naive variations of those language features. 18 | - image: /img/marius-ciocirlan-398931-unsplash.jpg 19 | links: 20 | - linkText: React Native 21 | linkURL: 'https://facebook.github.io/react-native/' 22 | name: Brian Kardashian 23 | presentationTitle: React & React Native 24 | text: >- 25 | The folks at Turbulent have been working hard using React and React-Native 26 | to bring quality, cross platform JS products to market. Today, they will 27 | show us the patterns, structures and challenges of building large scale 28 | cross platform apps. 29 | location: 30 | mapsLatitude: 64.843779 31 | mapsLink: 'https://goo.gl/maps/Rm6ihxVrZGK2' 32 | mapsLongitude: -147.718189 33 | name: Fairbanks Ice Museum 34 | --- 35 | 36 | -------------------------------------------------------------------------------- /src/pages/navbar/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | templateKey: navbar 3 | menuItems: 4 | - label: home 5 | linkType: internal 6 | linkURL: / 7 | - label: about 8 | linkType: internal 9 | linkURL: /about 10 | - label: past meetups 11 | linkType: internal 12 | linkURL: /meetups 13 | --- 14 | 15 | -------------------------------------------------------------------------------- /src/pages/pastMeetups/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | templateKey: past-meetups-page 3 | title: Past Meetups 4 | path: /meetups 5 | seo: 6 | browserTitle: Past Meetups | JS Wakanda 7 | description: View the topics that were presented at past JavaScript Montreal meetups. 8 | title: Past Meetups | JavaScript Wakanda 9 | --- 10 | 11 | Here are some of the subjects we've covered in past meetups. If you're interested in participating by giving a talk, don't worry too much if we've touched on the subject before. New people join every day and there are a lot of subjects warranting a re-visit. 12 | -------------------------------------------------------------------------------- /src/styles/404.scss: -------------------------------------------------------------------------------- 1 | @import "./variables.scss"; 2 | @import "./mixins.scss"; 3 | 4 | .pageNotFound { 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | padding: $section-padding 0; 9 | 10 | @include respond-to("small") { 11 | padding: $section-padding-mobile 0; 12 | } 13 | 14 | &-title { 15 | @include large-font(); 16 | @include title-link-border(); 17 | margin-bottom: 40px; 18 | } 19 | 20 | &-description { 21 | font-size: 0.7rem; 22 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", 23 | monospace; 24 | white-space: pre; 25 | margin-bottom: 40px; 26 | 27 | @include respond-to("small") { 28 | @include medium-font(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/styles/about-page.scss: -------------------------------------------------------------------------------- 1 | @import "./variables.scss"; 2 | @import "./mixins.scss"; 3 | 4 | .about { 5 | &-header { 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | padding-top: 40px; 10 | 11 | @include respond-to("medium") { 12 | padding-top: 60px; 13 | flex-direction: row; 14 | justify-content: space-around; 15 | } 16 | } 17 | 18 | &-titleWrapper { 19 | margin-bottom: 40px; 20 | margin-right: 20px; 21 | text-align: center; 22 | flex-shrink: 0; 23 | 24 | @include respond-to("medium") { 25 | flex-basis: 450px; 26 | text-align: left; 27 | margin-bottom: 0; 28 | justify-content: space-around; 29 | } 30 | } 31 | 32 | &-title { 33 | @include xlarge-font(); 34 | @include title-link-border(); 35 | display: inline; 36 | } 37 | 38 | &-imageWrapper { 39 | width: 400px; 40 | max-width: 100%; 41 | 42 | @include respond-to("medium") { 43 | width: 500px; 44 | } 45 | } 46 | 47 | &-description { 48 | padding: 0 20px 60px; 49 | text-align: justify; 50 | @include medium-font(); 51 | 52 | @include respond-to("small") { 53 | text-align: left; 54 | } 55 | 56 | h2 { 57 | text-align: center; 58 | margin-bottom: 20px; 59 | @include large-font(); 60 | } 61 | } 62 | } 63 | 64 | .developerGroups { 65 | background-color: $background-medium; 66 | color: $body-light; 67 | @include medium-font(); 68 | 69 | h2 { 70 | @include large-font(); 71 | margin-bottom: 40px; 72 | } 73 | 74 | ul li { 75 | margin-bottom: 20px; 76 | } 77 | 78 | a { 79 | @include link-border(); 80 | } 81 | } 82 | 83 | .galleryList { 84 | display: flex; 85 | flex-wrap: wrap; 86 | justify-content: space-around; 87 | margin-bottom: -20px; 88 | } 89 | 90 | .galleryList-item { 91 | width: 350px; 92 | margin-bottom: 20px; 93 | } 94 | 95 | .organizers { 96 | @include medium-font(); 97 | 98 | &-title { 99 | text-align: center; 100 | margin-bottom: 40px; 101 | @include large-font(); 102 | 103 | @include respond-to("small") { 104 | margin-bottom: 80px; 105 | } 106 | } 107 | 108 | &-list { 109 | display: flex; 110 | flex-wrap: wrap; 111 | justify-content: space-around; 112 | } 113 | 114 | &-listItem { 115 | display: flex; 116 | flex-direction: column; 117 | align-items: center; 118 | margin: 0 10px; 119 | } 120 | 121 | &-listItemImage { 122 | margin-bottom: 20px; 123 | border-radius: 3px; 124 | height: 200px; 125 | 126 | @include respond-to("small") { 127 | height: 300px; 128 | } 129 | } 130 | 131 | &-listItemName { 132 | font-weight: 400; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/styles/generic.scss: -------------------------------------------------------------------------------- 1 | @import "./variables.scss"; 2 | @import "./mixins.scss"; 3 | 4 | html { 5 | font-size: 16px; 6 | -moz-osx-font-smoothing: grayscale; 7 | -webkit-font-smoothing: antialiased; 8 | text-rendering: optimizeLegibility; 9 | text-size-adjust: 100%; 10 | background-color: $background-light; 11 | } 12 | 13 | body { 14 | font-family: BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", 15 | "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif; 16 | color: $body-dark; 17 | } 18 | 19 | a { 20 | color: inherit; 21 | text-decoration: none; 22 | } 23 | 24 | .container { 25 | max-width: $container-width; 26 | padding: 0 $container-padding; 27 | margin: auto; 28 | } 29 | 30 | .section { 31 | padding: $section-padding-mobile 0; 32 | 33 | @include respond-to("small") { 34 | padding: $section-padding 0; 35 | } 36 | } 37 | 38 | main { 39 | min-height: calc(100vh - #{$navbar-height} - #{$footer-height}); 40 | } 41 | -------------------------------------------------------------------------------- /src/styles/home.scss: -------------------------------------------------------------------------------- 1 | @import "./variables.scss"; 2 | @import "./mixins.scss"; 3 | 4 | .header { 5 | height: 450px; 6 | background-color: $background-medium; 7 | 8 | @include respond-to("medium") { 9 | height: 575px; 10 | } 11 | 12 | &-container { 13 | display: flex; 14 | flex-direction: column; 15 | height: 100%; 16 | align-items: center; 17 | justify-content: center; 18 | padding-top: 40px; 19 | padding-bottom: 40px; 20 | 21 | @include respond-to("small") { 22 | flex-direction: row; 23 | justify-content: space-around; 24 | } 25 | } 26 | 27 | &-image { 28 | height: 150px; 29 | margin-bottom: 30px; 30 | 31 | @include respond-to("small") { 32 | height: 200px; 33 | margin-bottom: 0; 34 | } 35 | 36 | @include respond-to("medium") { 37 | height: 300px; 38 | } 39 | } 40 | 41 | &-tagline { 42 | @include xlarge-font(); 43 | color: $body-light; 44 | width: 400px; 45 | text-align: center; 46 | cursor: default; 47 | 48 | @include respond-to("small") { 49 | flex-basis: 430px; 50 | text-align: left; 51 | width: 50%; 52 | align-items: flex-start; 53 | } 54 | 55 | @include respond-to("medium") { 56 | flex-basis: 500px; 57 | } 58 | } 59 | 60 | &-taglinePart { 61 | display: inline; 62 | @include title-link-border(); 63 | } 64 | } 65 | 66 | .upcomingMeetup { 67 | color: $body-dark; 68 | @include medium-font; 69 | 70 | &-container { 71 | display: flex; 72 | flex-direction: column; 73 | align-items: center; 74 | } 75 | 76 | &-title { 77 | @include large-font; 78 | margin-bottom: 30px; 79 | } 80 | 81 | &-detail { 82 | text-align: center; 83 | 84 | &--date { 85 | margin-bottom: 10px; 86 | } 87 | } 88 | 89 | &-detailLabel { 90 | font-weight: 400; 91 | } 92 | 93 | &-presenters { 94 | display: flex; 95 | flex-wrap: wrap; 96 | justify-content: space-around; 97 | padding: 40px 0; 98 | } 99 | 100 | &-presenter { 101 | display: flex; 102 | flex-direction: column; 103 | align-items: center; 104 | width: 300px; 105 | max-width: 100%; 106 | padding: 20px; 107 | border: 3px solid rgba($highlight, 0.2); 108 | margin: 20px; 109 | } 110 | 111 | &-presenterImage { 112 | height: 130px; 113 | border-radius: 100px; 114 | margin-bottom: 10px; 115 | } 116 | 117 | &-presenterName { 118 | font-weight: 400; 119 | margin-bottom: 5px; 120 | } 121 | 122 | &-presenterPresentationTitle { 123 | @include small-font; 124 | font-weight: 400; 125 | align-self: flex-start; 126 | margin-bottom: 5px; 127 | } 128 | 129 | &-presenterDescription { 130 | @include small-font; 131 | } 132 | 133 | &-mapWrapper { 134 | position: relative; 135 | height: 450px; 136 | width: 100%; 137 | } 138 | 139 | &-mapNote { 140 | text-align: center; 141 | margin-bottom: 10px; 142 | @include small-font(); 143 | } 144 | } 145 | 146 | .ctaBlock { 147 | position: relative; 148 | color: $body-light; 149 | 150 | @include respond-to("medium") { 151 | height: 300px; 152 | @include medium-font(); 153 | } 154 | 155 | &-pattern { 156 | display: flex; 157 | position: relative; 158 | 159 | @include respond-to("medium") { 160 | position: absolute; 161 | align-items: flex-end; 162 | transition: box-shadow $transition-duration cubic-bezier(0.4, 0, 0.3, 1); 163 | } 164 | 165 | &--first { 166 | background-color: $highlight-secondary; 167 | 168 | @include respond-to("medium") { 169 | justify-content: flex-end; 170 | top: 0; 171 | left: 0; 172 | width: 50%; 173 | height: 100%; 174 | } 175 | } 176 | &--second { 177 | background-color: $background-medium; 178 | 179 | @include respond-to("medium") { 180 | bottom: 0; 181 | right: 0; 182 | width: 50%; 183 | height: 100%; 184 | } 185 | } 186 | 187 | &:hover { 188 | z-index: 10; 189 | box-shadow: 0 4px 15px 5px rgba(0, 0, 0, 0.25); 190 | } 191 | } 192 | 193 | &-container { 194 | height: 100%; 195 | position: relative; 196 | } 197 | 198 | &-cta { 199 | display: flex; 200 | flex-direction: column; 201 | justify-content: flex-end; 202 | padding: 40px; 203 | height: 200px; 204 | 205 | @include respond-to("medium") { 206 | position: absolute; 207 | height: unset; 208 | width: 100vw / 2; 209 | } 210 | 211 | @include respond-to("large") { 212 | width: $container-width / 2; 213 | } 214 | } 215 | 216 | &-ctaHeading { 217 | @include large-font(); 218 | } 219 | 220 | &-ctaDescription { 221 | @include medium-font(); 222 | 223 | @include respond-to("medium") { 224 | margin-bottom: 40px; 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/styles/index.js: -------------------------------------------------------------------------------- 1 | import "./generic.scss"; 2 | import "./reset.scss"; 3 | -------------------------------------------------------------------------------- /src/styles/meetup.scss: -------------------------------------------------------------------------------- 1 | @import "./variables.scss"; 2 | @import "./mixins.scss"; 3 | 4 | .pastMeetups { 5 | &-container { 6 | padding: $section-padding-mobile 20px; 7 | display: flex; 8 | flex-direction: column; 9 | margin-bottom: -40px; 10 | 11 | @include respond-to("medium") { 12 | padding: $section-padding 20px; 13 | } 14 | } 15 | 16 | &-title { 17 | @include xlarge-font(); 18 | @include title-link-border(); 19 | margin-bottom: 20px; 20 | text-align: center; 21 | align-self: center; 22 | 23 | @include respond-to("medium") { 24 | margin-bottom: 40px; 25 | } 26 | } 27 | 28 | &-description { 29 | @include medium-font(); 30 | width: 900px; 31 | max-width: 100%; 32 | margin: 0 auto 20px; 33 | } 34 | 35 | &-meetup { 36 | margin: 40px 0; 37 | } 38 | } 39 | 40 | .meetup { 41 | border: 3px solid rgba($highlight, 0.2); 42 | padding: 10px; 43 | @include medium-font(); 44 | 45 | @include respond-to("medium") { 46 | padding: 40px; 47 | flex-direction: row; 48 | } 49 | 50 | &-title { 51 | @include large-font(); 52 | text-align: center; 53 | margin-bottom: 20px; 54 | } 55 | 56 | &-label { 57 | font-weight: 400; 58 | } 59 | 60 | &-meta { 61 | display: flex; 62 | flex-direction: column; 63 | justify-content: center; 64 | margin-bottom: 30px; 65 | 66 | @include respond-to("medium") { 67 | flex-direction: row; 68 | } 69 | } 70 | 71 | &-metaField { 72 | margin: 0 20px; 73 | text-align: center; 74 | 75 | &--date { 76 | margin-bottom: 10px; 77 | 78 | @include respond-to("medium") { 79 | margin-bottom: 0; 80 | } 81 | } 82 | } 83 | 84 | &-presenters { 85 | display: flex; 86 | flex-wrap: wrap; 87 | justify-content: center; 88 | margin-bottom: -10px; 89 | 90 | @include respond-to("medium") { 91 | margin-bottom: 0x; 92 | margin: 0 -20px; 93 | } 94 | } 95 | 96 | &-presenter { 97 | display: flex; 98 | align-items: flex-start; 99 | padding-bottom: 30px; 100 | 101 | @include respond-to("medium") { 102 | flex-basis: 50%; 103 | padding: 0 20px; 104 | } 105 | } 106 | 107 | &-presenterImageContainer { 108 | display: flex; 109 | flex-direction: column; 110 | align-items: center; 111 | flex-shrink: 0; 112 | margin-right: 20px; 113 | flex-basis: 100px; 114 | 115 | @include respond-to("medium") { 116 | margin-right: 20px; 117 | } 118 | } 119 | 120 | &-presenterInfo { 121 | display: flex; 122 | flex-direction: column; 123 | align-items: flex-start; 124 | } 125 | 126 | &-presenterTitle { 127 | font-weight: 400; 128 | margin-bottom: 10px; 129 | } 130 | 131 | &-presenterImage { 132 | height: 100px; 133 | border-radius: calc(100px / 2); 134 | margin-bottom: 10px; 135 | 136 | @include respond-to("medium") { 137 | height: 100px; 138 | border-radius: calc(100px / 2); 139 | margin-bottom: 20px; 140 | } 141 | } 142 | 143 | &-presenterName { 144 | @include small-font(); 145 | font-weight: 400; 146 | text-align: center; 147 | word-break: break-word; 148 | } 149 | 150 | &-presenterText { 151 | @include small-font(); 152 | } 153 | 154 | &-presenterLinks { 155 | margin-top: 5px; 156 | } 157 | 158 | &-presenterLinkItem { 159 | margin-bottom: 5px; 160 | } 161 | 162 | &-presenterLink { 163 | @include small-font(); 164 | @include link-border(); 165 | font-weight: 400; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/styles/mixins.scss: -------------------------------------------------------------------------------- 1 | @import "./variables.scss"; 2 | 3 | $breakpoints: ( 4 | "small": 767px, 5 | "medium": 992px, 6 | "large": 1200px, 7 | ) !default; 8 | 9 | @mixin respond-to($breakpoint) { 10 | // If the key exists in the map 11 | @if map-has-key($breakpoints, $breakpoint) { 12 | // Prints a media query based on the value 13 | @media (min-width: map-get($breakpoints, $breakpoint)) { 14 | @content; 15 | } 16 | } 17 | 18 | // If the key doesn't exist in the map 19 | @else { 20 | @warn "Unfortunately, no value could be retrieved from `#{$breakpoint}`. " 21 | + "Available breakpoints are: #{map-keys($breakpoints)}."; 22 | } 23 | } 24 | 25 | @mixin small-font() { 26 | font-weight: 300; 27 | font-size: 0.9rem; 28 | letter-spacing: 0.4px; 29 | line-height: 1.4rem; 30 | 31 | @include respond-to("small") { 32 | font-size: 1rem; 33 | letter-spacing: 0.5px; 34 | line-height: 1.5rem; 35 | } 36 | } 37 | 38 | @mixin medium-font() { 39 | font-weight: 300; 40 | font-size: 1rem; 41 | letter-spacing: 0.5px; 42 | line-height: 1.5rem; 43 | 44 | @include respond-to("small") { 45 | font-size: 1.25rem; 46 | letter-spacing: 1px; 47 | line-height: 1.75rem; 48 | } 49 | } 50 | 51 | @mixin large-font() { 52 | font-weight: 400; 53 | font-size: 1.75rem; 54 | letter-spacing: 1px; 55 | line-height: 3rem; 56 | 57 | @include respond-to("small") { 58 | font-size: 2.25rem; 59 | letter-spacing: 1.2px; 60 | line-height: 3.75rem; 61 | } 62 | } 63 | 64 | @mixin xlarge-font() { 65 | font-weight: 400; 66 | font-size: 2rem; 67 | letter-spacing: 0.5px; 68 | line-height: 3rem; 69 | 70 | @include respond-to("small") { 71 | font-size: 2.5rem; 72 | letter-spacing: 1px; 73 | line-height: 3.5rem; 74 | } 75 | 76 | @include respond-to("medium") { 77 | font-size: 3rem; 78 | line-height: 4rem; 79 | } 80 | } 81 | 82 | @mixin link-border() { 83 | border-bottom: 2px solid $highlight; 84 | transition: all $transition-duration ease-in-out; 85 | 86 | &:hover { 87 | background-color: $highlight; 88 | } 89 | } 90 | 91 | @mixin title-link-border() { 92 | border-bottom: 3px solid $highlight; 93 | } 94 | -------------------------------------------------------------------------------- /src/styles/past-meetups-page.scss: -------------------------------------------------------------------------------- 1 | @import "./variables.scss"; 2 | @import "./mixins.scss"; 3 | 4 | .pastMeetups { 5 | &-container { 6 | padding: $section-padding-mobile 20px; 7 | display: flex; 8 | flex-direction: column; 9 | margin-bottom: -40px; 10 | 11 | @include respond-to("medium") { 12 | padding: $section-padding 20px; 13 | } 14 | } 15 | 16 | &-title { 17 | @include xlarge-font(); 18 | @include title-link-border(); 19 | margin-bottom: 20px; 20 | text-align: center; 21 | align-self: center; 22 | 23 | @include respond-to("medium") { 24 | margin-bottom: 40px; 25 | } 26 | } 27 | 28 | &-description { 29 | @include medium-font(); 30 | width: 900px; 31 | max-width: 100%; 32 | margin: 0 auto 20px; 33 | } 34 | 35 | &-meetup { 36 | margin: 20px 0; 37 | 38 | @include respond-to("medium") { 39 | margin-bottom: 40px; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/styles/reset.scss: -------------------------------------------------------------------------------- 1 | /*! minireset.css v0.0.3 | MIT License | github.com/jgthms/minireset.css */ 2 | // Blocks 3 | html, 4 | body, 5 | p, 6 | ol, 7 | ul, 8 | li, 9 | dl, 10 | dt, 11 | dd, 12 | blockquote, 13 | figure, 14 | fieldset, 15 | legend, 16 | textarea, 17 | pre, 18 | iframe, 19 | hr, 20 | h1, 21 | h2, 22 | h3, 23 | h4, 24 | h5, 25 | h6 { 26 | margin: 0; 27 | padding: 0; 28 | } 29 | 30 | // Headings 31 | h1, 32 | h2, 33 | h3, 34 | h4, 35 | h5, 36 | h6 { 37 | font-weight: normal; 38 | font-size: 100%; 39 | } 40 | 41 | // List 42 | ul { 43 | list-style: none; 44 | } 45 | 46 | // Form 47 | button, 48 | input, 49 | select, 50 | textarea { 51 | margin: 0; 52 | } 53 | 54 | // Box sizing 55 | html { 56 | box-sizing: border-box; 57 | } 58 | 59 | * { 60 | &, 61 | &::before, 62 | &::after { 63 | box-sizing: inherit; 64 | } 65 | } 66 | 67 | // Media 68 | img, 69 | audio, 70 | video { 71 | height: auto; 72 | max-width: 100%; 73 | } 74 | 75 | // Iframe 76 | iframe { 77 | border: 0; 78 | } 79 | 80 | // Table 81 | table { 82 | border-collapse: collapse; 83 | border-spacing: 0; 84 | } 85 | 86 | td, 87 | th { 88 | padding: 0; 89 | text-align: left; 90 | } 91 | -------------------------------------------------------------------------------- /src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | $white: #fff; 2 | $american-silver: #cecece; 3 | $sambucus: #1b1d2e; 4 | $black-blueberry: #2d3047; 5 | $coronation: #ececec; 6 | $golden-apricot: #e0a458; 7 | $greenway: #419d78; 8 | $snowy-mountain: #f2efea; 9 | 10 | $highlight: $golden-apricot; 11 | $highlight-secondary: $greenway; 12 | 13 | $background-light: $snowy-mountain; 14 | $background-dark: $sambucus; 15 | $background-medium: $black-blueberry; 16 | 17 | $menu-active: $white; 18 | $menu-inactive: $american-silver; 19 | 20 | $body-light: $coronation; 21 | $body-dark: $black-blueberry; 22 | 23 | $navbar-height: 50px; 24 | $footer-height: 500px; 25 | $container-padding: 20px; 26 | $section-padding: 80px; 27 | $section-padding-mobile: 40px; 28 | $container-width: 1200px; 29 | $transition-duration: 250ms; 30 | -------------------------------------------------------------------------------- /src/templates/about-page.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { graphql } from "gatsby"; 4 | import ReactMarkdown from "react-markdown"; 5 | import Helmet from "react-helmet"; 6 | 7 | import Layout from "../components/Layout"; 8 | import HTMLContent from "../components/Content"; 9 | import "../styles/about-page.scss"; 10 | 11 | export const AboutPageTemplate = props => { 12 | const { page } = props; 13 | 14 | return ( 15 |
16 |
17 |
18 |
19 |

{page.frontmatter.title}

20 |
21 |
22 | {page.frontmatter.mainImage.imageAlt} 23 |
24 |
25 |
26 | {/* The page.html is actually markdown when viewing the page from the netlify CMS, 27 | so we must use the ReactMarkdown component to parse the markdown in that case */} 28 | {page.bodyIsMarkdown ? ( 29 | 30 | ) : ( 31 | 32 | )} 33 |
    34 | {page.frontmatter.gallery.map((galleryImage, index) => ( 35 |
  • 36 | {galleryImage.imageAlt} 37 |
  • 38 | ))} 39 |
40 |
41 |
42 |
43 |
44 | 45 |
46 |
47 |
48 |
49 |

{page.frontmatter.organizers.title}

50 |
    51 | {page.frontmatter.organizers.gallery.map((galleryImage, index) => ( 52 |
  • 53 | {galleryImage.imageAlt} 58 | {galleryImage.name} 59 |
  • 60 | ))} 61 |
62 |
63 |
64 |
65 | ); 66 | }; 67 | 68 | const AboutPage = ({ data }) => { 69 | const { markdownRemark: page, footerData, navbarData } = data; 70 | const { 71 | frontmatter: { 72 | seo: { title: seoTitle, description: seoDescription, browserTitle }, 73 | }, 74 | } = page; 75 | 76 | return ( 77 | 78 | 79 | 80 | 81 | {browserTitle} 82 | 83 | 84 | 85 | ); 86 | }; 87 | 88 | AboutPage.propTypes = { 89 | data: PropTypes.object.isRequired, 90 | }; 91 | 92 | export default AboutPage; 93 | 94 | export const aboutPageQuery = graphql` 95 | query AboutPage($id: String!) { 96 | markdownRemark(id: { eq: $id }) { 97 | html 98 | frontmatter { 99 | title 100 | mainImage { 101 | image 102 | imageAlt 103 | } 104 | gallery { 105 | image 106 | imageAlt 107 | } 108 | developerGroups 109 | organizers { 110 | title 111 | gallery { 112 | image 113 | imageAlt 114 | name 115 | } 116 | } 117 | seo { 118 | browserTitle 119 | title 120 | description 121 | } 122 | } 123 | } 124 | ...LayoutFragment 125 | } 126 | `; 127 | -------------------------------------------------------------------------------- /src/templates/meetup.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import "../styles/meetup.scss"; 4 | 5 | import HeadshotPlaceholder from "../img/headshot-placeholder.svg"; 6 | 7 | class MeetupTemplate extends Component { 8 | render() { 9 | return ( 10 |
14 |

{this.props.meetup.title}

15 |
16 |

17 | Date: {this.props.meetup.formattedDate} 18 |

19 |

20 | Location: {this.props.meetup.location.name} 21 |

22 |
23 |
24 | {this.props.meetup.presenters.map(presenter => ( 25 |
26 |
27 | {presenter.image 32 | {presenter.name} 33 |
34 |
35 | {presenter.presentationTitle && ( 36 |

{presenter.presentationTitle}

37 | )} 38 |

{presenter.text}

39 | 49 |
50 |
51 | ))} 52 |
53 |
54 | ); 55 | } 56 | } 57 | 58 | MeetupTemplate.propTypes = { 59 | meetup: PropTypes.shape({ 60 | title: PropTypes.string, 61 | name: PropTypes.string, 62 | presenters: PropTypes.array, 63 | }), 64 | }; 65 | 66 | export default MeetupTemplate; 67 | -------------------------------------------------------------------------------- /src/templates/past-meetups-page.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { graphql } from "gatsby"; 4 | import Helmet from "react-helmet"; 5 | import isBefore from "date-fns/is_before"; 6 | import ReactMarkdown from "react-markdown"; 7 | 8 | import MeetupTemplate from "./meetup"; 9 | import Layout from "../components/Layout"; 10 | import HTMLContent from "../components/Content"; 11 | import "../styles/past-meetups-page.scss"; 12 | 13 | export const PastMeetupsPageTemplate = ({ 14 | title, 15 | content, 16 | meetups = null, 17 | bodyIsMarkdown = false, 18 | }) => { 19 | return ( 20 |
21 |
22 |

{title}

23 | {bodyIsMarkdown ? ( 24 | 25 | ) : ( 26 | 27 | )} 28 | {meetups && 29 | meetups.map((meetup, index) => ( 30 | 35 | ))} 36 |
37 |
38 | ); 39 | }; 40 | 41 | PastMeetupsPageTemplate.propTypes = { 42 | title: PropTypes.string.isRequired, 43 | content: PropTypes.string, 44 | meetups: PropTypes.array, 45 | }; 46 | 47 | const PastMeetupsPage = ({ data }) => { 48 | const { markdownRemark: page } = data; 49 | const { 50 | frontmatter: { 51 | seo: { title: seoTitle, description: seoDescription, browserTitle }, 52 | }, 53 | } = page; 54 | let meetups = data.allMarkdownRemark.edges; 55 | 56 | // Find all the meetups that occured in the past 57 | meetups = meetups.filter(meetup => { 58 | return isBefore(meetup.node.frontmatter.rawDate, new Date()) && meetup; 59 | }); 60 | 61 | return ( 62 | 63 | 64 | 65 | 66 | {browserTitle} 67 | 68 | 73 | 74 | ); 75 | }; 76 | 77 | PastMeetupsPage.propTypes = { 78 | data: PropTypes.object.isRequired, 79 | }; 80 | 81 | export default PastMeetupsPage; 82 | 83 | export const pastMeetupsPageQuery = graphql` 84 | query PastMeetupsPage($id: String!) { 85 | markdownRemark(id: { eq: $id }) { 86 | html 87 | frontmatter { 88 | title 89 | seo { 90 | browserTitle 91 | title 92 | description 93 | } 94 | } 95 | } 96 | ...LayoutFragment 97 | allMarkdownRemark( 98 | filter: { frontmatter: { presenters: { elemMatch: { text: { ne: null } } } } } 99 | sort: { order: DESC, fields: frontmatter___date } 100 | ) { 101 | edges { 102 | node { 103 | frontmatter { 104 | title 105 | formattedDate: date(formatString: "MMMM Do YYYY @ h:mm A") 106 | rawDate: date 107 | presenters { 108 | name 109 | image 110 | text 111 | presentationTitle 112 | links { 113 | linkText 114 | linkURL 115 | } 116 | } 117 | location { 118 | name 119 | } 120 | } 121 | } 122 | } 123 | } 124 | } 125 | `; 126 | -------------------------------------------------------------------------------- /static/admin/config.yml: -------------------------------------------------------------------------------- 1 | backend: 2 | name: git-gateway 3 | branch: master 4 | 5 | media_folder: static/img 6 | public_folder: /img 7 | 8 | display_url: https://gatsby-netlify-cms-example.netlify.com/ 9 | 10 | collections: 11 | - name: "meetups" 12 | label: "Meetups" 13 | description: "Meetup dates, location, and presenter information." 14 | folder: "src/pages/meetups" 15 | create: true 16 | fields: 17 | - { label: "Template Key", name: "templateKey", widget: "hidden", default: "meetup" } 18 | - { label: "Title", name: "title", widget: "string" } 19 | - { label: "Date", name: "date", widget: "datetime" } 20 | - { 21 | label: Presenters, 22 | name: presenters, 23 | required: true, 24 | widget: list, 25 | fields: 26 | [ 27 | { label: Name, name: name, required: true, widget: string }, 28 | { 29 | label: Presentation Title, 30 | name: presentationTitle, 31 | required: false, 32 | widget: string, 33 | }, 34 | { 35 | label: Image, 36 | name: image, 37 | required: false, 38 | hint: "If an image isn't specified, a default headshot placeholder image will be used", 39 | widget: image, 40 | }, 41 | { label: Text, name: text, required: true, widget: text }, 42 | { 43 | label: Links, 44 | name: links, 45 | required: false, 46 | widget: list, 47 | fields: 48 | [ 49 | { label: Text, name: linkText, widget: string }, 50 | { label: URL, name: linkURL, widget: string }, 51 | ], 52 | }, 53 | ], 54 | } 55 | - { 56 | label: "Location", 57 | name: "location", 58 | required: true, 59 | widget: "object", 60 | fields: 61 | [ 62 | { label: "Name", name: "name", widget: "string" }, 63 | { label: "Google Maps Link", name: "mapsLink", widget: "string" }, 64 | { label: "Latitude", name: "mapsLatitude", widget: "number", valueType: "float" }, 65 | { label: "Longitude", name: "mapsLongitude", widget: "number", valueType: "float" }, 66 | ], 67 | } 68 | - name: "pages" 69 | label: "Pages" 70 | files: 71 | - file: "src/pages/about/index.md" 72 | label: "About" 73 | name: "about" 74 | fields: 75 | - { label: "Template Key", name: "templateKey", widget: "hidden", default: "about-page" } 76 | - { label: "Title", name: "title", widget: "string" } 77 | - { 78 | label: "Main Image", 79 | name: "mainImage", 80 | widget: "object", 81 | fields: 82 | [ 83 | { label: "Image", name: "image", widget: "image" }, 84 | { label: "Image Description", name: "imageAlt", widget: "string" }, 85 | ], 86 | } 87 | - { label: "Body", name: "body", widget: "markdown" } 88 | - { 89 | label: "Image Gallery", 90 | name: "gallery", 91 | widget: "list", 92 | fields: 93 | [ 94 | { label: "Image", name: "image", widget: "image" }, 95 | { label: "Image Description", name: "imageAlt", widget: "string" }, 96 | ], 97 | } 98 | - { label: "Developer Groups", name: "developerGroups", widget: "markdown" } 99 | - { 100 | label: "Meetup Organizers", 101 | name: "organizers", 102 | widget: "object", 103 | fields: 104 | [ 105 | { label: "Title", name: "title", widget: "string" }, 106 | { 107 | label: "Images", 108 | name: "gallery", 109 | widget: "list", 110 | fields: 111 | [ 112 | { label: "Name", name: "name", widget: "string" }, 113 | { label: "Image", name: "image", widget: "image" }, 114 | { label: "Image Description", name: "imageAlt", widget: "string" }, 115 | ], 116 | }, 117 | ], 118 | } 119 | - { 120 | label: "SEO & Meta", 121 | name: "seo", 122 | widget: "object", 123 | fields: 124 | [ 125 | { label: "Browser Tab Title", name: "browserTitle", widget: "string"}, 126 | { label: "Title", name: "title", widget: "string" }, 127 | { label: "Description", name: "description", widget: "string" }, 128 | ], 129 | } 130 | - file: "src/pages/pastMeetups/index.md" 131 | label: "Past Meetups" 132 | name: "pastMeetups" 133 | fields: 134 | - { 135 | label: "Template Key", 136 | name: "templateKey", 137 | widget: "hidden", 138 | default: "past-meetups-page", 139 | } 140 | - { label: "Title", name: "title", widget: "string" } 141 | - { label: "Body", name: "body", widget: "markdown" } 142 | - { 143 | label: "SEO & Meta", 144 | name: "seo", 145 | widget: "object", 146 | fields: 147 | [ 148 | { label: "Browser Tab Title", name: "browserTitle", widget: "string"}, 149 | { label: "Title", name: "title", widget: "string" }, 150 | { label: "Description", name: "description", widget: "string" }, 151 | ], 152 | } 153 | - file: "src/pages/home/index.md" 154 | label: "Home" 155 | name: "home" 156 | fields: 157 | - { label: "Template Key", name: "templateKey", widget: "hidden", default: "home-page" } 158 | - { 159 | label: "Header Image", 160 | name: "headerImage", 161 | widget: "object", 162 | fields: 163 | [ 164 | { label: "Image", name: "image", widget: "image" }, 165 | { label: "Image Description", name: "imageAlt", widget: "string" }, 166 | ], 167 | } 168 | - { label: "Title", name: "title", widget: "string" } 169 | - { label: "Upcoming Meetup Heading", name: "upcomingMeetupHeading", widget: "string" } 170 | - { label: "No Upcoming Meetup Text", name: "noUpcomingMeetupText", hint: "This text will be displayed when there are no upcoming meetups", required: true, widget: "string" } 171 | - { label: "Maps Note", name: "mapsNote", required: true, widget: "string" } 172 | - { 173 | label: "Call to Actions", 174 | name: "callToActions", 175 | required: true, 176 | widget: "object", 177 | fields: 178 | [ 179 | { 180 | label: "First CTA", 181 | name: "firstCTA", 182 | required: true, 183 | widget: "object", 184 | fields: 185 | [ 186 | { label: "Heading", name: "heading", widget: "string" }, 187 | { label: "Sub Heading", name: "subHeading", widget: "string" }, 188 | { 189 | label: "Link Type", 190 | name: "linkType", 191 | widget: "select", 192 | options: ["internal", "external"], 193 | }, 194 | { 195 | label: "Link URL", 196 | name: "linkURL", 197 | widget: "string", 198 | hint: "Use a relative URL (e.g. /about) if the link is an internal link.", 199 | } 200 | ] 201 | }, 202 | { 203 | label: "Second CTA", 204 | name: "secondCTA", 205 | required: true, 206 | widget: "object", 207 | fields: 208 | [ 209 | { label: "Heading", name: "heading", widget: "string" }, 210 | { label: "Sub Heading", name: "subHeading", widget: "string" }, 211 | { 212 | label: "Link Type", 213 | name: "linkType", 214 | widget: "select", 215 | options: ["internal", "external"], 216 | }, 217 | { 218 | label: "Link URL", 219 | name: "linkURL", 220 | widget: "string", 221 | hint: "Use a relative URL (e.g. /about) if the link is an internal link.", 222 | } 223 | ] 224 | } 225 | ], 226 | } 227 | - { 228 | label: "SEO & Meta", 229 | name: "seo", 230 | widget: "object", 231 | fields: 232 | [ 233 | { label: "Browser Tab Title", name: "browserTitle", widget: "string"}, 234 | { label: "Title", name: "title", widget: "string" }, 235 | { label: "Description", name: "description", widget: "string" }, 236 | ], 237 | } 238 | - name: "navbarAndFooter" 239 | label: "Navbar & Footer" 240 | files: 241 | - file: "src/pages/navbar/index.md" 242 | label: "Navbar" 243 | name: "navbar" 244 | fields: 245 | - { label: "Template Key", name: "templateKey", widget: "hidden", default: "navbar" } 246 | - { 247 | label: "Menu Items", 248 | name: "menuItems", 249 | widget: "list", 250 | fields: 251 | [ 252 | { label: "Label", name: "label", widget: "string" }, 253 | { label: "Link Type", name: "linkType", widget: "select", options: ["internal", "external"] }, 254 | { label: "Link URL", name: "linkURL", widget: "string", hint: "Use a relative URL (e.g. /about) if the link is an internal link." }, 255 | ], 256 | } 257 | - file: "src/pages/footer/index.md" 258 | label: "Footer" 259 | name: "footer" 260 | fields: 261 | - { label: "Template Key", name: "templateKey", widget: "hidden", default: "footer" } 262 | - { 263 | label: "Logo Image & Tagline", 264 | name: "logoImage", 265 | widget: "object", 266 | fields: 267 | [ 268 | { label: "Image", name: "image", widget: "image" }, 269 | { label: "Image Description", name: "imageAlt", widget: "string" }, 270 | { label: "Tagline", name: "tagline", widget: "string" }, 271 | ], 272 | } 273 | - { 274 | label: "Social Links", 275 | name: "socialLinks", 276 | widget: "list", 277 | fields: 278 | [ 279 | { label: "Image", name: "image", widget: "image" }, 280 | { label: "Image Description", name: "imageAlt", widget: "string" }, 281 | { label: "Label", name: "label", widget: "string" }, 282 | { label: "Link URL", name: "linkURL", widget: "string" }, 283 | ], 284 | } -------------------------------------------------------------------------------- /static/img/annie-spratt-608001-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/annie-spratt-608001-unsplash.jpg -------------------------------------------------------------------------------- /static/img/benjamin-parker-736167-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/benjamin-parker-736167-unsplash.jpg -------------------------------------------------------------------------------- /static/img/edward-cisneros-415601-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/edward-cisneros-415601-unsplash.jpg -------------------------------------------------------------------------------- /static/img/email.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/img/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 12 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /static/img/humphrey-muleba-795250-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/humphrey-muleba-795250-unsplash.jpg -------------------------------------------------------------------------------- /static/img/jakob-dalbjorn-730178-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/jakob-dalbjorn-730178-unsplash.jpg -------------------------------------------------------------------------------- /static/img/jonas-kakaroto-577554-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/jonas-kakaroto-577554-unsplash.jpg -------------------------------------------------------------------------------- /static/img/js-wakanda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/js-wakanda.png -------------------------------------------------------------------------------- /static/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/img/lucas-sankey-378674-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/lucas-sankey-378674-unsplash.jpg -------------------------------------------------------------------------------- /static/img/marius-ciocirlan-398931-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/marius-ciocirlan-398931-unsplash.jpg -------------------------------------------------------------------------------- /static/img/meetup.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 21 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 80 | 81 | 83 | 85 | 87 | 89 | 91 | 94 | 96 | 98 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /static/img/michael-dam-258165-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/michael-dam-258165-unsplash.jpg -------------------------------------------------------------------------------- /static/img/neonbrand-509131-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/neonbrand-509131-unsplash.jpg -------------------------------------------------------------------------------- /static/img/organizer-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/organizer-1.jpg -------------------------------------------------------------------------------- /static/img/organizer-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/organizer-2.jpg -------------------------------------------------------------------------------- /static/img/ramy-kabalan-796973-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/ramy-kabalan-796973-unsplash.jpg -------------------------------------------------------------------------------- /static/img/teemu-paananen-376238-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/teemu-paananen-376238-unsplash.jpg -------------------------------------------------------------------------------- /static/img/television.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertcoopercode/gatsby-netlify-cms/c9609693887ec2fb986a2d52fc6f94d8716b1cb5/static/img/television.png -------------------------------------------------------------------------------- /static/img/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 13 | 40 | 42 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | --------------------------------------------------------------------------------