<file_sep>import styled from "styled-components";

const Paragraph = styled.p`
  font-size: ${props => (props.bold ? "1.5rem" : "1.2rem")};
  font-weight: 500;
  margin-bottom: 1.5rem;
  font-weight: ${props => (props.bold ? "600" : "500")};
`;

export default Paragraph;
<file_sep>import React, { useEffect } from "react"

import {
  ModalBackground,
  ModalContainer,
  ModalHeader,
  ModalHeaderTitle,
  ModalHeaderClose,
} from "./styles"
import IconCross from "../icons/Cross"
import { useSpring, animated } from "react-spring"

const AnimatedModalContainer = animated(ModalContainer)

export default function Modal({ isOpen, onClose, title, children }) {
  const contentProps = useSpring({
    opacity: isOpen ? 1 : 0,
    config: { duration: 100 },
  })

  useEffect(() => {
    window.addEventListener("keydown", onEscKeyDown, false)

    return () => {
      window.removeEventListener("keydown", onEscKeyDown, false)
    }
  }, [isOpen])

  const onEscKeyDown = e => {
    if (e.key !== "Escape") return

    onClose()
  }

  if (isOpen) {
    return (
      <ModalBackground
        data-testid="modal-background"
        open={isOpen}
        onClick={onClose}
      >
        <AnimatedModalContainer
          style={contentProps}
          onClick={event => event.stopPropagation()}
        >
          <ModalHeader>
            <ModalHeaderTitle>{title || ""}</ModalHeaderTitle>
            <ModalHeaderClose
              data-testid="modal-header-close"
              onClick={onClose}
            >
              <IconCross />
            </ModalHeaderClose>
          </ModalHeader>
          {children}
        </AnimatedModalContainer>
      </ModalBackground>
    )
  } else {
    typeof document !== `undefined` &&
      document.body.style.removeProperty(`overflow-y`)
    return null
  }
}
<file_sep>import * as Yup from "yup"

const phoneRegExp = /^(\+?\d{0,4})?\s?-?\s?(\(?\d{3}\)?)\s?-?\s?(\(?\d{3}\)?)\s?-?\s?(\(?\d{4}\)?)?$/

export const validationSchema = Yup.object().shape({
  name: Yup.string()
    .min(2, "Too Short!")
    .max(50, "Too Long!")
    .required("Required"),
  email: Yup.string()
    .email("Invalid email")
    .required("Required"),
  location: Yup.string().required("Required"),
  phone: Yup.string()
    .matches(phoneRegExp, "Phone number is not valid")
    .required("Required"),
  subject: Yup.string().required("Required"),
})

export const options = [
  { value: "info", label: "General Info" },
  { value: "workshop", label: "Workshop" },
  { value: "personal-training", label: "Personal Training" },
  { value: "nutrition", label: "Nutrition" },
  { value: "disc", label: "DISC – Personality Test" },
]

export const initialValues = {
  email: "",
  name: "",
  location: "",
  phone: "",
  subject: "info",
  message: "",
}
<file_sep>import styled from "styled-components"

const Title = styled.h1`
  width: 100%;
  text-align: center;
  margin: 0 0 1.5rem 0;
`

export default Title
<file_sep>import React from "react"
import { render } from "@testing-library/react"
import Icon from "../Icon.css.js"
import theme from "../theme"

describe("Button", () => {
  it("renders correctly", () => {
    const { asFragment } = render(<Icon />)

    expect(asFragment()).toMatchSnapshot()
  })

  it("renders correctly with theme", () => {
    const { asFragment, container } = render(<Icon theme={theme} />)

    expect(container.firstChild).toHaveStyleRule("fill", theme.textColor)

    expect(asFragment()).toMatchSnapshot()
  })
})
<file_sep>import React from "react"
import { render } from "@testing-library/react"
import Form from "../index.js"

describe("Form", () => {
  it("renders correctly", () => {
    const { asFragment } = render(<Form />)

    expect(asFragment()).toMatchSnapshot()
  })
})
<file_sep>import React from "react";
import styled from "styled-components";
import { ErrorMessage } from "formik";

const StyledErrorMessage = styled(ErrorMessage)`
  color: #d8000c;
  font-size: 0.75rem;
  margin-top: 0.15rem;
`;

const InputGroup = styled.div`
  margin-bottom: 0.5rem;
`;

const Label = styled.label`
  display: block;
  font-size: 0.75rem;
  margin: 0.25rem 0;
`;

export default props => (
  <InputGroup>
    <Label>{props.label}</Label>
    {props.children}
    <StyledErrorMessage {...props.error} />
  </InputGroup>
);
<file_sep>import React from "react"
import { render } from "@testing-library/react"
import ArrowDown from "../ArrowDown.js"

describe("Icon/arrow-down", () => {
  it("renders correctly", () => {
    const { asFragment } = render(<ArrowDown />)

    expect(asFragment()).toMatchSnapshot()
  })
})
<file_sep>import styled from "styled-components"

const Icon = styled.svg`
  display: block;
  fill: ${props => props.theme.textColor};
  width: 1rem;
  height: 1rem;
`

export default Icon
<file_sep>import React from "react"
import Icon from "../styled/Icon.css.js"

export default props => (
  <Icon {...props} viewBox="0 0 32 32">
    <path d="M16 31l15-15h-9v-16h-12v16h-9z"></path>
  </Icon>
)
<file_sep>import styled from "styled-components"

const Content = styled.div`
  @media (min-width: 768px) {
    padding: 1rem 2rem;
  }
`

export default Content
<file_sep>import React from "react"
import styled from "styled-components"
import { graphql, useStaticQuery } from "gatsby"

import Title from "./styled/Title.css.js"
import Section from "./styled/Section.css.js"
import Content from "./styled/Content.css.js"

const Label = styled.label`
  display: block;
  margin-bottom: 0.25rem;
`
const Value = styled.div`
  display: block;
  font-weight: 600;
  max-width: 500px;
  margin: 0 auto;
`

const Block = styled.div`
  margin-bottom: 1rem;
`

const Inner = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
`

export default () => {
  const data = useStaticQuery(graphql`
    query ContactInfoQuery {
      contactJson {
        email
        title
        phone
        address
      }
    }
  `)
  const { title, email, phone, address } = data.contactJson

  return (
    <Section>
      <Title>{title}</Title>
      <Inner>
        <Content style={{ textAlign: "center" }}>
          <Block>
            <Label>Email:</Label>

            <Value>
              <a href={`mailto:${email}`}>{email}</a>
            </Value>
          </Block>
          <Block>
            <Label>Phone:</Label>
            <Value>{phone}</Value>
          </Block>
          <Block>
            <Label>Address:</Label>
            <Value>{address}</Value>
          </Block>
        </Content>
      </Inner>
    </Section>
  )
}
<file_sep>import React from "react"
import { render } from "@testing-library/react"
import Button from "../Button.css.js"
import theme from "../theme"

describe("Button", () => {
  it("renders correctly", () => {
    const { asFragment, container } = render(<Button>Label</Button>)

    expect(container.firstChild).not.toHaveStyleRule("display", "block")
    expect(container.firstChild).not.toHaveStyleRule("width", "100%")

    expect(asFragment()).toMatchSnapshot()
  })

  it("renders correctly with block modifier", () => {
    const props = { block: true }
    const { asFragment, container } = render(<Button {...props}>Label</Button>)

    expect(container.firstChild).toHaveStyleRule("display", "block")
    expect(container.firstChild).toHaveStyleRule("width", "100%")

    expect(asFragment()).toMatchSnapshot()
  })

  it("renders correctly with theme", () => {
    const props = { theme }
    const { asFragment } = render(<Button {...props}>Label</Button>)

    expect(asFragment()).toMatchSnapshot()
  })
})
<file_sep>import React from "react";
import styled from "styled-components";

const UL = styled.ul`
  font-size: 1.2rem;

  & > li {
    margin-bottom: 2rem;
  }
`;

export default props => (
  <UL>
    {props.items.map((item, index) => (
      <li key={index}>{item}</li>
    ))}
  </UL>
);
<file_sep>import React from "react"
import { render } from "@testing-library/react"
import Paragraph from "../Paragraph.css.js"

describe("Paragraph", () => {
  it("renders correctly", () => {
    const { asFragment, container } = render(<Paragraph>Content</Paragraph>)

    expect(container.firstChild).toHaveStyleRule("font-weight", "500")
    expect(container.firstChild).toHaveStyleRule("font-size", "1.2rem")

    expect(asFragment()).toMatchSnapshot()
  })

  it("renders correctly with bold", () => {
    const { asFragment, container } = render(
      <Paragraph bold={true}>Content</Paragraph>
    )

    expect(container.firstChild).toHaveStyleRule("font-weight", "600")
    expect(container.firstChild).toHaveStyleRule("font-size", "1.5rem")

    expect(asFragment()).toMatchSnapshot()
  })
})
<file_sep>import React, { useState } from "react"
import qs from "query-string"
import PresentationContainer from "./PresentationContainer"

export default () => {
  const [isModalOpen, setIsModalOpen] = useState(false)
  const [successModal, setSuccessModal] = useState(false)

  const handleModalOpenClick = () => {
    setIsModalOpen(true)
  }

  const handleModalCloseClick = () => {
    setIsModalOpen(false)
  }

  const handleSuccessModalOpen = () => {
    setSuccessModal(true)
  }

  const handleSuccessModalClose = () => {
    setSuccessModal(false)
  }

  const handleFormSubmit = (values, setSubmitting) => {
    fetch("/", {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: qs.stringify({ "form-name": "contact", ...values }),
    })
      .then(() => {
        setSubmitting(false)
        setIsModalOpen(false)
        handleSuccessModalOpen()
      })
      .catch(error => console.error(error))
  }

  return (
    <PresentationContainer
      isModalOpen={isModalOpen}
      onModalOpenClick={handleModalOpenClick}
      onModalCloseClick={handleModalCloseClick}
      onFormSubmit={handleFormSubmit}
      successModal={successModal}
      onSuccessModalClose={handleSuccessModalClose}
    />
  )
}
<file_sep>import React from "react"
import { Formik, Form } from "formik"

import InputGroup from "../common/InputGroup"
import Select from "../common/Select"
import Button from "../styled/Button.css.js"
import Input from "../styled/TextInput.css.js"
import Textarea from "../styled/Textarea.css.js"

import { validationSchema, options, initialValues } from "../../helper/form"

export default ({ onFormSubmit }) => {
  return (
    <Formik
      initialValues={initialValues}
      validationSchema={validationSchema}
      onSubmit={(values, { setSubmitting }) => {
        onFormSubmit(values, setSubmitting)
      }}
    >
      {({ isSubmitting, values, handleChange, handleBlur }) => (
        <Form
          name="contact"
          method="post"
          data-netlify="true"
          data-netlify-honeypot="bot-field"
        >
          <input type="hidden" name="form-name" value="contact" />
          <InputGroup
            label="Full Name"
            error={{ name: "name", component: "div" }}
          >
            <Input
              onChange={handleChange}
              onBlur={handleBlur}
              type="text"
              name="name"
              value={values.name}
              placeholder="<NAME>"
            />
          </InputGroup>

          <InputGroup label="Email" error={{ name: "email", component: "div" }}>
            <Input
              onChange={handleChange}
              onBlur={handleBlur}
              type="email"
              name="email"
              value={values.email}
              placeholder="<EMAIL>"
            />
          </InputGroup>

          <InputGroup
            label="Location"
            error={{ name: "location", component: "div" }}
          >
            <Input
              onChange={handleChange}
              onBlur={handleBlur}
              type="text"
              name="location"
              value={values.location}
              placeholder="Los Angeles, California"
            />
          </InputGroup>

          <InputGroup
            label="Phone Number"
            error={{ name: "phone", component: "div" }}
          >
            <Input
              onChange={handleChange}
              onBlur={handleBlur}
              type="text"
              name="phone"
              value={values.phone}
              placeholder="+x xxx xxx xxxx"
            />
          </InputGroup>

          <InputGroup
            label="Subject"
            error={{ name: "subject", component: "div" }}
          >
            <Select
              name="subject"
              value={values.subject}
              options={options}
              onChange={handleChange}
              onBlur={handleBlur}
            />
          </InputGroup>

          <InputGroup
            label="Additional Information"
            error={{ name: "message", component: "div" }}
          >
            <Textarea
              onChange={handleChange}
              onBlur={handleBlur}
              name="message"
              value={values.message}
              placeholder="Message"
            />
          </InputGroup>

          <Button block type="submit" disabled={isSubmitting}>
            {isSubmitting ? "Submitting..." : "Submit Request"}
          </Button>
        </Form>
      )}
    </Formik>
  )
}
<file_sep>module.exports = {
  siteMetadata: {
    title: `sportconsulting.ca`,
    description: `Sportconsulting.ca provides services for athletes and teams that help improve their performance and achieve goals.`,
    keywords: `sport consulting, players, teams, professional, semi-pro, amateur, personal trainer, sport nutrition, DISC, personality test, athlete performance, baseball, basketball, football, hockey, soccer, lacrosse, Frisbee ultimate, tennis, swimming, try-out, college`,
  },
  plugins: [
    `gatsby-plugin-react-helmet`,
    `gatsby-transformer-json`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `data`,
        path: `${__dirname}/src/data`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    {
      resolve: `gatsby-plugin-styled-components`,
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          {
            resolve: `gatsby-remark-images-contentful`,
            options: {
              maxWidth: 600,
              linkImagesToOriginal: false,
              withWebp: true,
            },
          },
        ],
      },
    },
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `sportconsulting.ca`,
        short_name: `sportconsulting`,
        start_url: `/`,
        background_color: `#1171b7`,
        theme_color: `#1171b7`,
        display: `minimal-ui`,
        icon: `src/images/logo_sq.png`, // This path is relative to the root of the site.
      },
    },
    {
      resolve: `gatsby-source-contentful`,
      options: {
        spaceId: process.env.CONTENTFUL_SPACE_ID,
        accessToken: process.env.CONTENTFUL_DELIVERY_ACCESS_TOKEN,
      },
    },
    {
      resolve: `gatsby-plugin-google-gtag`,
      options: {
        trackingIds: [process.env.GA_TRACKING_ID],
        gtagConfig: {
          anonymize_ip: true,
        },
      },
    },

    // this (optional) plugin enables Progressive Web App + Offline functionality
    // To learn more, visit: https://gatsby.dev/offline
    `gatsby-plugin-offline`,
  ],
}
<file_sep>import React from "react"
import { render } from "@testing-library/react"
import UnorderedList from "../UnorderedList.js"

describe("UnorderedList", () => {
  it("renders correctly without items", () => {
    const { asFragment } = render(<UnorderedList items={[]} />)

    expect(asFragment()).toMatchSnapshot()
  })

  it("renders correctly with items", () => {
    const { asFragment } = render(
      <UnorderedList items={["item 1", "item 2"]} />
    )

    expect(asFragment()).toMatchSnapshot()
  })
})
<file_sep>import { createGlobalStyle } from "styled-components";

const GlobalStyle = createGlobalStyle`
  @import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600,700&display=swap');

  * {
    scroll-behavior: smooth;
    box-sizing: border-box;
  }
  body {
    font-family: 'Source Sans Pro', sans-serif;
    font-size: 16px;
    margin: 0 auto;
    padding: 0;
    max-width: 1200px;
    color: ${props => props.theme.textColor};
  }
  
  a {
    text-decoration: none;
    color: ${props => props.theme.primaryColor};
    font-weight: bold;

    &:hover {
        color: ${props => props.theme.primaryColorHover};
    }
  }

  img {
    max-width: 100%;
  }
`;

export default GlobalStyle;
<file_sep>import { lighten } from "polished";

const primaryColor = "#1171b7";

const theme = {
  primaryColor,
  primaryColorHover: lighten(0.1, primaryColor),
  borderColor: "#ccc",
  textColor: "#444444"
};

export default theme;
<file_sep>const Input = `
  border: thin solid;
  display: block;
  padding: 0.5em;
  background: #fff;
  width: 100%;
`;

export default Input;
<file_sep>import React from "react"
import { render } from "@testing-library/react"
import IconCross from "../Cross.js"

describe("Icon/cross", () => {
  it("renders correctly", () => {
    const { asFragment } = render(<IconCross />)

    expect(asFragment()).toMatchSnapshot()
  })
})
