<|start_filename|>src/styled/theme.js<|end_filename|>

const primary = '#7D97E5'
const primaryDark = '#323D5C'
const greyLight = '#E9EEFE'
const greyDark = '#6F6069'
const fontSizes = [
  14, 17, 19, 23, 32, 28, 44, 56
]
const space = [
  0, 4, 8, 16, 32, 64, 128, 256
]

const serif = '"Maitree", system-ui, sans-serif'
const sans = '"Hind", system-ui, sans-serif'

export default {
  fontSizes: fontSizes,
  space: space,
  fonts: { sans, serif },
  breakpoints: ['40em', '52em', '64em'],
  colors: {
    primary: primary,
    primaryDark: primaryDark,
    greyLight: greyLight,
    greyDark: greyDark,
  },
  shadows: {
    small: '0 0 4px rgba(0, 0, 0, .225)',
    large: '0 0 24px rgba(0, 0, 0, .225)'
  },
  Button: {
    cursor: 'pointer',
    textAlign: 'center',
    fontSize: fontSizes[2],
    paddingTop: space[3],
    paddingBottom: space[3],
    paddingLeft: space[4],
    paddingRight: space[4],
  },
  buttons: {
    primary: {
      color: '#fff',
      backgroundColor: primary,
      '&:hover': {
        color: '#fff',
        backgroundColor: primaryDark,
      }
    },
    white: {
      color: '#fff',
      backgroundColor: 'rgba(255,255,255,0.2)',
      boxShadow: 'inset 0 0 0 2px',
      fontWeight: 400,
      '&:hover': {
        color: primary,
        backgroundColor: '#fff'
      }
    }
  },
  cards: {
    grey: {
      backgroundColor: greyLight,
    },
    pricing: {
      border: '3px solid ' + primary,
      padding: space[4],
      textAlign: 'center',
    },
    primary: {
      padding: space[4],
      textAlign: 'center',
      backgroundColor: primary,
      color: 'white',
      boxShadow: '0 0 24px rgba(0, 0, 0, .125)',
    },
    primaryDark: {
      padding: space[4],
      textAlign: 'center',
      backgroundColor: primaryDark,
      color: 'white',
      boxShadow: '0 0 24px rgba(0, 0, 0, .125)',
    }
  },
  Heading: {
    fontFamily: serif,
    color: primaryDark,
  },
  headings: {
    h1: {
      fontSize: fontSizes[7],
      fontWeight: 700,
    },
    h2: {
      fontSize: fontSizes[6],
      fontWeight: 700,
    },
    h3: {
      fontSize: fontSizes[5],
      fontWeight: 700,
    },
    h4: {
      fontSize: fontSizes[3],
      fontWeight: 700,
    }
  }
}

<|start_filename|>src/components/LogoutButton/LogoutButton.js<|end_filename|>
import React, { Component } from 'react'
import gql from 'graphql-tag'
import { Link } from '@magicsoup.io/stock'
import { navigate } from '@reach/router'
import Cookies from 'universal-cookie'
import store from '../../redux/store'
import client from '../../apollo/client'

import { logoutUser } from '../../redux/actions/userActions';

class LogoutButton extends Component {
  logout = () => {
    client.mutate({
      mutation: LOGOUT,
    }).then(({ data }) => {
      if (data.logout) {
        navigate('/')
      }
    })
    store.dispatch(logoutUser())
    const cookies = new Cookies()
    cookies.remove('bearer_token')
  }

  render() {
    const { children } = this.props
    return (
      <Link {...this.props} onClick={this.logout}>
        {children}
      </Link>
    )
  }
}

export default LogoutButton

const LOGOUT = gql`
  mutation Logout {
    logout
  }
`


<|start_filename|>src/styled/elements/Label.js<|end_filename|>
// styled-components based extension
import React from 'react'
import styled from 'styled-components'
import { Text } from '@magicsoup.io/stock'

const Label = styled(Text)`
  line-height: 1.55;
  display: flex;
  flex-direction: column;
  text-transform: uppercase;

  strong{
    font-weight: 700;
  }
`

Label.defaultProps = {
  as: 'label',
  color: 'greyDark',
  fontSize: 1,
  m: 0,
  p: 0,
  pt: 3,
}

export default Label

<|start_filename|>src/apollo/config.js<|end_filename|>
export const apolloConfig = {
  networkInterface: process.env.APOLLO_API_URL
}

export default apolloConfig

<|start_filename|>src/data/hero/hero.json<|end_filename|>
{
  "displayTitle": "Gatsby Redux SaaS starter",
  "subTitle": "A Gatsby Redux SaaS starter for your next SaaS product. Uses react-redux, apollo-client, magicsoup.io, styled-components, styled-system."
}

<|start_filename|>src/styled/index.js<|end_filename|>
export { default as Section } from './blocks/Section'
export { default as Footer } from './blocks/Footer'

export { default as Menu } from './elements/Menu'
export { default as P } from './elements/P'
export { default as Label } from './elements/Label'
export { default as Input } from './elements/Input'


<|start_filename|>src/redux/store.js<|end_filename|>
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import logger from 'redux-logger'
import rootReducer from './reducers'

const initialState = {}
const middleware = [thunk]

if (process.env.NODE_ENV === 'development'){
  middleware.push(logger)
}

const store = createStore(
  rootReducer,
  initialState,
  compose(
    applyMiddleware(...middleware),
    // window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), // eslint-disable-line no-underscore-dangle
  ),
)

export default store

<|start_filename|>src/pages/404.js<|end_filename|>
import React from 'react'
import { Link } from 'gatsby'
import {
  Layout,
  SEO,
} from '../components'

import {
 P
} from '../styled'

import { Heading, Container } from '@magicsoup.io/stock'

const NotFoundPage = () => (
  <Layout>
    <Container>
      <SEO title='404: Not found' />
      <Heading as='h1' variant='h1'>404 Page not found</Heading>
      <P fontSize={3}>You just hit a route that doesn&#39;t exist... the sadness.</P>
      <P fontSize={3}><Link to='/'>Bring me back home...</Link></P>
    </Container>
  </Layout>
)

export default NotFoundPage


<|start_filename|>src/styled/blocks/Footer.js<|end_filename|>
// styled-components based extension
import styled from 'styled-components'
import { Box } from '@magicsoup.io/stock'

const Footer = styled(Box)`
  a{
    color: white;
    text-decoration: underline;
  }
`

Footer.defaultProps = {
  as: 'footer',
  bg: 'primary',
}

export default Footer

<|start_filename|>src/components/Hero/Hero.js<|end_filename|>
import React from 'react'
import { Link } from 'gatsby'
import { StaticQuery, graphql } from 'gatsby'
import { Flex, Box, Card, Heading, Text } from '@magicsoup.io/stock'
import { P } from '../../styled'
import { themeGet } from 'styled-system'


const Hero = () => (
  <StaticQuery
    query={graphql`
      query HeroQuery {
        heroJson {
          displayTitle
          subTitle
        }
      }
    `}
    render={data => (
      <Card >
        <Flex
          ml={[2,5,6]}
          pl={4}
          my={[5, 5]}
          alignItems='center'
          style={{borderLeft: `5px dotted #7D97E5`}}
        >
          <Box>
            <Heading as='h1' variant='h1' fontSize={[5,6,7]} m={0}>
              {data.heroJson.displayTitle}
            </Heading>
            <P fontSize={3} mb={3}>
              {data.heroJson.subTitle}
            </P>
            
          </Box>
        </Flex>
        <Flex
          alignItems='center'
          flexWrap='wrap'
        >
          <Card variant='primary' p={3} width={[1, 3/5]} style={{ '&:hover': { margin: 1}}}>
            <Heading as='h2' variant='h2'>
              Gatsby Starter
            </Heading>
            <P fontSize={3} mb={4}>Redux, Apollo, Authentication, SEO friendly, GWT, Responsive, Markdown, Static content, Web-Fonts, SSR, Testing</P>
            <Flex justifyContent='center' alignItems='center'>
              <Box 
                as='a' 
                className='github-button' 
                data-icon='octicon-star'
                href='https://github.com/zauberware/gatsby-starter-redux-saas' 
                data-size='large' 
                data-show-count='true' 
                aria-label={`Star this repo on GitHub`}
                >
                Star on GitHub
              </Box>
              <Box pl={4}>
                <Link to='/pages/readme'>
                  <Text color='white'>Go to Readme →</Text>
                </Link>
              </Box>
            </Flex>
          </Card>

          <Card variant='primaryDark' p={3} width={[1, 2/5]} mx={[3, 0]}>
            <Heading as='h2' variant='h3' style={{color: 'white'}}>
              RubyOnRails Backend
            </Heading>
            <P fontSize={3} mb={4}>Rails 5, Authentication with devise + JWT, GraphQL, CORS, SSL</P>
            <Flex justifyContent='center' alignItems='center'>
              <Box 
                as='a'
                className='github-button' 
                data-icon='octicon-star'
                href='https://github.com/zauberware/rails-devise-graphql' 
                data-size='large' 
                data-show-count='true' 
                aria-label={`Star this repo on GitHub`}>
                Star on GitHub
              </Box>
              <Box pl={4} as='a' href='https://github.com/zauberware/rails-devise-graphql' color='white'>
                Go to Readme →
              </Box>
            </Flex>
          </Card>
        
        </Flex>
      </Card>
    )}
  />
)

export default Hero


<|start_filename|>src/redux/reducers/index.js<|end_filename|>
import { combineReducers } from 'redux'
import userReducer from './userReducer'

const appReducer = combineReducers({
  user: userReducer,
})

export default function (state, action) {
  if (action.type === 'SIGNOUT_USER') {
    state = undefined // eslint-disable-line no-param-reassign
  }
  return appReducer(state, action)
}


<|start_filename|>src/components/LogoutButton/index.js<|end_filename|>
import LogoutButton from './LogoutButton'
export default LogoutButton

<|start_filename|>src/components/Login/Login.js<|end_filename|>
import React, { Fragment } from 'react'
import { Redirect } from '@reach/router'
import Form from '../Form'
import { Container, Card, Heading } from '@magicsoup.io/stock'
import { connect } from 'react-redux'

const mapStateToProps = (props) => {
  const { user } = props
  return user
}

class Login extends React.Component {

  render() {
    const { user } = this.props
    if(user && user.email){
      return <Redirect to='/app/profile' noThrow />
    }else{
      return (
        <Container maxWidth={800}>
          <Card variant='grey' p={4} pt={5} my={5}>
            <Heading variant='h1' as='h1'>Log in</Heading>
            <Form />
          </Card>
        </Container>
      )
    }
  }
}

export default connect(
  mapStateToProps,
)(Login)


<|start_filename|>src/redux/actions/types.js<|end_filename|>
export const LOADING = 'LOADING'
export const LOGIN_USER = 'LOGIN_USER'
export const LOGOUT_USER = 'LOGOUT_USER'
export const REGISTER_USER = 'REGISTER_USER'


<|start_filename|>gatsby-config.js<|end_filename|>
require("dotenv").config({
  path: `.env.${process.env.NODE_ENV}`,
})

module.exports = {
  siteMetadata: {
    title: `Gatsby Redux SaaS starter`,
    description: `A Gatsby starter for SaaS products. Uses redux and apollo and a graphql token auth backend.`,
    author: `@zauberware`,
    image: `website-preview.jpg`,
    siteUrl: `https://gatsby-redux.zauberware.com`,
    googleSiteVerification: `[place-google-webmaster-tools-key-here]`,
  },
  plugins: [
    `gatsby-plugin-react-helmet`,
    {
      resolve: 'gatsby-plugin-web-font-loader',
      options: {
        google: {
          families: ['Maitree:300,400,700', 'Hind:400,700']
        }
      }
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `GatsbyJS Starter with MagicSoup.io`,
        short_name: `gatsby.magicsoup.io`,
        start_url: `/`,
        background_color: `#663399`,
        theme_color: `#663399`,
        display: `minimal-ui`,
        icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
      },
    },
    `gatsby-transformer-remark`,
    `gatsby-transformer-json`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/data/`,
      },
    },
    {
      resolve: `gatsby-plugin-sitemap`,
      options: {
        output: `/sitemap.xml`,
        // Exclude specific pages or groups of pages using glob parameters
        // See: https://github.com/isaacs/minimatch
        // The example below will exclude the single `path/to/page` and all routes beginning with `category`
        query: `
          {
            site {
              siteMetadata {
                siteUrl
              }
            }
  
            allSitePage {
              edges {
                node {
                  path
                }
              }
            }
        }`
      },
    },
    `gatsby-plugin-styled-components`,
    // this (optional) plugin enables Progressive Web App + Offline functionality
    // To learn more, visit: https://gatsby.app/offline
    // 'gatsby-plugin-offline',
  ],
}


<|start_filename|>src/components/Footer/__tests__/Footer.spec.js<|end_filename|>
import React from 'react'
import renderer from 'react-test-renderer'
import { StaticQuery } from 'gatsby'
import Footer from '../Footer'

beforeEach(() => {
  StaticQuery.mockImplementationOnce(({ render }) =>
    render({
      generalJson: {
        footer: {
          elements: [
            {
              headline: 'Home',
              text: 'A text in the footer',
            },
            {
              headline: 'Contact',
              text: 'A text in the footer',
            }
          ]
        }
      },
    })
  )
})

describe('Footer', () => {
  it('renders correctly', () => {
    const tree = renderer.create(<Footer />).toJSON()
    expect(tree).toMatchSnapshot()
  })
})

<|start_filename|>src/styled/global-styles.js<|end_filename|>
import React, { Fragment } from 'react'
import { css, createGlobalStyle } from 'styled-components'
import styledNormalize from 'styled-normalize'
import { themeGet } from 'styled-system'

const GlobalStyle = createGlobalStyle(props => css`
  ${styledNormalize}
  
  body {
    font-size: 17px;
    font-family: ${props.theme.fonts.sans};
    -webkit-font-smoothing: antialiased;
    font-smoothing: antialiased;
    font-weight: 400;
    overflow-x: hidden;
    width: 100%;
  }

  a{
    color: ${themeGet('colors.primary')};
    text-decoration: none;

    &:hover{
      text-decoration: underline;
    }
  }

  code{
    font-size: 0.95rem;
    font-weight: 300;
    padding: 3px;
    margin: 0 3px;
    border-radius: 4px;
    line-height: 1;
    background-color: ${themeGet('colors.greyLight')};
  }

`)
export default () => (
  <Fragment>
    <GlobalStyle />
  </Fragment>
)


<|start_filename|>src/styled/blocks/Section.js<|end_filename|>
// styled-components based extension
import styled from 'styled-components'
import { Box } from '@magicsoup.io/stock'

const Section = styled(Box)`

`

Section.defaultProps = {
  py: 5
}

export default Section

<|start_filename|>src/components/Hero/__tests__/Hero.spec.js<|end_filename|>
import React from "react"
import renderer from "react-test-renderer"
import { StaticQuery } from 'gatsby'
import Hero from "../Hero"

beforeEach(() => {
  StaticQuery.mockImplementationOnce(({ render }) =>
    render({
      heroJson: {
        displayTitle: 'Display title',
        subTitle: 'Subtitle',
      },
    })
  )
})

describe("Hero", () => {
  it("renders correctly", () => {
    const tree = renderer.create(<Hero />).toJSON()
    expect(tree).toMatchSnapshot()
  })
})

<|start_filename|>src/components/Image/index.js<|end_filename|>
import Image from './Image'

export default Image

<|start_filename|>src/components/Layout/__tests__/Layout.spec.js<|end_filename|>
import React from "react"
import renderer from "react-test-renderer"
import { StaticQuery } from 'gatsby'
import Layout from "../Layout"

jest.mock('../../Header', () => ()=> <header id="mockHeader">
   mockHeader
</header>)

jest.mock('../../Footer', () => ()=> <footer id="mockFooter">
   mockFooter
</footer>)

beforeEach(() => {
  StaticQuery.mockImplementationOnce(({ render }) =>
    render({
      site: {
        siteMetadata: {
          title: 'Website Title',
        },
      },
    })
  )

})

describe("Layout", () => {
  it("renders correctly", () => {
    const tree = renderer.create(<Layout>Some child</Layout>).toJSON()
    expect(tree).toMatchSnapshot()
  })
})

<|start_filename|>src/redux/actions/userActions.js<|end_filename|>
import {
  LOGIN_USER,
  LOGOUT_USER,
  LOADING,
  REGISTER_USER,
} from './types'

export const loginUser = (user) => (dispatch) => {
  dispatch({
    type: LOADING,
  })
  // Login goes here
  dispatch({
    type: LOGIN_USER,
    payload: user,
  })
}

export const logoutUser = () => (dispatch) => {
  dispatch({
    type: LOADING,
  })
  // Logout goes here
  dispatch({
    type: LOGOUT_USER,
    payload: null,
  })
}


export const registerUser = (user) => (dispatch) => {
  dispatch({
    type: LOADING,
  })
  // Register goes here
  dispatch({
    type: REGISTER_USER,
    payload: user,
  })
}


<|start_filename|>src/components/Footer/Footer.js<|end_filename|>
import React from 'react';
import { StaticQuery, graphql } from 'gatsby'
import { 
  Box,
  Flex,
  Container,
  Heading,
} from '@magicsoup.io/stock'
import { 
  P,
  Footer as StyledFooter,
} from '../../styled'

const Footer = () => (
  <StaticQuery
    query={graphql`
      query {
        generalJson {
          footer {
            elements {
              headline
              text
            }
          }
        }
      }
    `}
    render={data => 
      <StyledFooter>
        <Container pt={5} pb={4} px={3}>
          <Flex flexWrap='wrap' flexDirection='column'>
            {data.generalJson.footer.elements.map((element, index) => (
              <Box 
                key={`footer-${index}`}
                px={3}
                pt={4}
                pb={3}
                width={[1]}>
                <Heading as='h4' variant='h4' color='white'>{element.headline}</Heading>
                <P color='white' dangerouslySetInnerHTML={{__html: element.text }} />
              </Box>
            ))}
          </Flex>
        </Container>
        <Box 
          bg='primaryDark'
          >
          <Container py={3}>
            <P 
              fontSize={1}
              color='white'
              mt={0}
              textAlign='center'
              >© 2018 - {new Date().getFullYear()} <a href="https://www.zauberware.com" target="_blank">zauberware technologies</a></P>
          </Container>
        </Box>
      </StyledFooter>
    }
  />
)

export default Footer

<|start_filename|>src/styled/elements/Menu.js<|end_filename|>
import styled, { css } from 'styled-components'
import { themeGet } from 'styled-system'
import { Box, Text } from '@magicsoup.io/stock'
import { Link as GatsbyLink } from "gatsby"

const Menu = styled(Box)(props => css`
  background-color: ${themeGet('colors.primary', 'purple')};
  font-family: ${themeGet('fonts.sans', 'Arial')};
  position: fixed;
  width: 100%;
  display: flex;
  transition: background-color 300ms linear;
  z-index: 1000;
`)

Menu.defaultProps = {
  as: 'nav'
}

export default Menu


<|start_filename|>src/components/Status/Status.js<|end_filename|>
import React, { Component } from 'react'
import { Link, navigate } from '@reach/router'
import { connect } from 'react-redux'
import { Container, Box, Text } from '@magicsoup.io/stock'

import { P } from '../../styled'
import LogoutButton from '../LogoutButton'


const mapStateToProps = (props) => {
  const { user } = props
  return user
}

class Status extends Component {

  render(){
    let details
    let color = this.props.color || 'inherit'
    let linkColor = this.props.linkColor || 'primary'
    const { user } = this.props

    if (!user) {
      details = (
        <P color={color} mt={0}>
          To get the full app experience, you’ll need to
          {` `}
          <Link to='/app/login' style={{color: linkColor }}>log in</Link>.
        </P>
      )
    } else {
      const { firstName, lastName, email } = user

      details = (
        <P color={color} mt={0}>
          Logged in as 
          {` `}
          <Link
            to='/app/profile'
            style={{ color: color }}
          >
            {firstName} {lastName}
          </Link>
          {` - `}
          <LogoutButton style={{ color: linkColor }}>Logout</LogoutButton>
        </P>
      )
    }

    return <div>{details}</div>
  }
}

export default connect(mapStateToProps)(Status)

<|start_filename|>src/components/Profile/Profile.js<|end_filename|>

import React, { Fragment, Component } from 'react'
import { Container, Box, Heading, Card } from '@magicsoup.io/stock'
import { P } from '../../styled'

import { connect } from 'react-redux'

const mapStateToProps = (props) => {
  const { user } = props
  return user
}

class Profile extends Component{

  render(){
    const { user } = this.props
    const { firstName, lastName, email } = user
    
    return (
      <Container>
        <Heading as='h1' variant='h1'>Welcome back, {firstName} {lastName}!</Heading>
        <Card 
          variant='grey' 
          maxWidth={600} 
          p={[4,5]} 
          mb={5}
          ml={[0,5]}>
          <Heading as='h2' variant='h3'>User Data:</Heading>
          <P>
            Firstname: {firstName}<br/>
            Lastname: {lastName}<br/>
            E-Mail: {email}
          </P>
        </Card>
      </Container>
    )
  }
}

export default connect(mapStateToProps)(Profile)

<|start_filename|>src/pages/index.js<|end_filename|>
import React from 'react'

import {
  Hero,
  Layout,
  SEO,
  Status,
} from '../components'

import { 
  Container ,
  Box,
} from '@magicsoup.io/stock'

import { Link } from 'gatsby'

const IndexPage = () => (
  <Layout>
    <Container>
      <SEO title='Home' keywords={[`gatsby`, `application`, `react`]} />
        <Hero />
        <Box py={6}>
          <hr/>
          <Status />
        </Box>
    </Container>
  </Layout>
)

export default IndexPage


<|start_filename|>src/components/Plans/index.js<|end_filename|>
import Plans from './Plans'

export default Plans


<|start_filename|>src/components/index.js<|end_filename|>
export { default as Header } from './Header'
export { default as Hero } from './Hero'
export { default as Image } from './Image'
export { default as Layout } from './Layout'
export { default as SEO } from './Seo'
export { default as Footer } from './Footer'

export { default as Status } from './Status'
export { default as Form } from './Form'
export { default as Profile } from './Profile'
export { default as Plans } from './Plans'
export { default as Login } from './Login'
export { default as LogoutButton } from './LogoutButton'
export { default as PrivateRoute } from './PrivateRoute'
