├── static
├── robots.txt
├── social.png
└── logo.svg
├── .prettierignore
├── screenshot.png
├── src
├── images
│ ├── author.jpg
│ └── icon.png
├── components
│ ├── Carousel.css
│ ├── Hero.jsx
│ ├── Cards.jsx
│ ├── Carousel.jsx
│ ├── Button.jsx
│ ├── Card.jsx
│ ├── SiteMetadata.jsx
│ ├── MenuMobile.jsx
│ ├── Header.jsx
│ ├── Footer.jsx
│ ├── Newsletter.jsx
│ └── Overlay.jsx
├── styles
│ └── style.css
├── layouts
│ └── Layout.jsx
├── pages
│ ├── 404.js
│ ├── index.js
│ └── about.js
└── templates
│ └── portfolio-item.jsx
├── postcss.config.js
├── .prettierrc
├── .env.example
├── .editorconfig
├── tailwind.config.js
├── bin
├── hello.js
└── setup.js
├── LICENSE
├── .gitignore
├── gatsby-node.js
├── gatsby-config.js
├── package.json
├── README.md
└── contentful
└── export.json
/static/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .cache
2 | package.json
3 | package-lock.json
4 | public
5 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wkocjan/gatsby-contentful-portfolio/HEAD/screenshot.png
--------------------------------------------------------------------------------
/static/social.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wkocjan/gatsby-contentful-portfolio/HEAD/static/social.png
--------------------------------------------------------------------------------
/src/images/author.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wkocjan/gatsby-contentful-portfolio/HEAD/src/images/author.jpg
--------------------------------------------------------------------------------
/src/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wkocjan/gatsby-contentful-portfolio/HEAD/src/images/icon.png
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = () => ({
2 | plugins: [require("tailwindcss"), require(`autoprefixer`)()],
3 | })
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "endOfLine": "lf",
3 | "semi": false,
4 | "singleQuote": false,
5 | "tabWidth": 2,
6 | "trailingComma": "es5"
7 | }
8 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | CONTENTFUL_SPACE_ID=''
2 | CONTENTFUL_ACCESS_TOKEN=''
3 |
4 | # https://www.gatsbyjs.org/packages/gatsby-plugin-mailchimp/?=mailchimp#mailchimp-endpoint
5 | MAILCHIMP_ENDPOINT=''
6 |
--------------------------------------------------------------------------------
/src/components/Carousel.css:
--------------------------------------------------------------------------------
1 | .swiper-container {
2 | --swiper-theme-color: theme("colors.blue.500");
3 | }
4 |
5 | .swiper-pagination-bullet {
6 | @apply .bg-white;
7 | }
8 |
9 | @screen md {
10 | .swiper-pagination-bullet {
11 | @apply .w-3 .h-3;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; top-most EditorConfig file
2 | root = true
3 |
4 | ; Unix-style newlines
5 | [*]
6 | end_of_line = LF
7 | charset = utf-8
8 | indent_style = space
9 | indent_size = 2
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/src/styles/style.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | body {
4 | @apply .bg-white .font-sans .antialiased;
5 | }
6 |
7 | @tailwind components;
8 |
9 | @tailwind utilities;
10 |
11 | .container {
12 | @apply .max-w-screen-xl .mx-auto .px-4;
13 | }
14 |
15 | @screen md {
16 | .container {
17 | @apply .px-6;
18 | }
19 | }
20 |
21 | @screen lg {
22 | .container {
23 | @apply .px-8;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: ["./src/**/*.jsx", "./src/**/*.js"],
3 | theme: {
4 | container: {
5 | center: true,
6 | padding: "1.25rem",
7 | },
8 | fontFamily: {
9 | sans: ["Inter var", "system-ui", "sans-serif"],
10 | },
11 | },
12 | variants: {
13 | opacity: ["responsive", "hover", "focus", "group-hover"],
14 | display: ["responsive", "hover", "focus", "last"],
15 | },
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Hero.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | const Hero = () => (
4 |
5 |
6 | Hello, I'm John{" "}
7 |
8 | 👋
9 |
10 |
11 |
12 | Welcome to my photography portfolio.
13 |
14 |
15 |
16 | )
17 |
18 | export default Hero
19 |
--------------------------------------------------------------------------------
/src/layouts/Layout.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types"
2 | import React from "react"
3 | import "typeface-inter"
4 | import "../styles/style.css"
5 | import Footer from "../components/Footer"
6 | import Header from "../components/Header"
7 |
8 | const Layout = ({ children }) => {
9 | return (
10 | <>
11 |
12 | {children}
13 |
14 | >
15 | )
16 | }
17 |
18 | Layout.propTypes = {
19 | children: PropTypes.node.isRequired,
20 | isHome: PropTypes.bool,
21 | }
22 |
23 | export default Layout
24 |
--------------------------------------------------------------------------------
/bin/hello.js:
--------------------------------------------------------------------------------
1 | const chalk = require("chalk")
2 | const pkg = require("../package.json")
3 |
4 | console.log(`
5 |
6 | ${chalk.green("Hey there! 👋")}
7 |
8 | Thanks for giving the ${pkg.name} a try. 🎉
9 | To get you going really quickly this project includes a setup step.
10 |
11 | ${chalk.yellow.bold("npm run setup")} automates the following steps for you:
12 | - creates a config file ${chalk.yellow(".env")}
13 | - imports ${chalk.green("a predefined content model")}
14 |
15 | When this is done run:
16 |
17 | ${chalk.yellow(
18 | "gatsby develop"
19 | )} to start a development environment at ${chalk.green("localhost:8000")}
20 |
21 | or
22 |
23 | ${chalk.yellow(
24 | "gatsby build"
25 | )} to create a production ready static site in ${chalk.green("./public")}
26 |
27 | `)
28 |
--------------------------------------------------------------------------------
/src/components/Cards.jsx:
--------------------------------------------------------------------------------
1 | import classNames from "classnames"
2 | import PropTypes from "prop-types"
3 | import React from "react"
4 | import Card from "./Card"
5 |
6 | const Cards = ({ items, hideLastItemOnMobile = false }) => {
7 | return (
8 |
9 |
10 | {items.map(item => (
11 |
17 |
18 |
19 | ))}
20 |
21 |
22 | )
23 | }
24 |
25 | Cards.propTypes = {
26 | items: PropTypes.arrayOf(PropTypes.object).isRequired,
27 | }
28 |
29 | export default Cards
30 |
--------------------------------------------------------------------------------
/src/pages/404.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import Layout from "../layouts/Layout"
3 | import SiteMetadata from "../components/SiteMetadata"
4 |
5 | const ErrorPage = () => (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Error 404
14 |
15 |
16 |
17 | The page doesn't exists.
18 |
19 |
20 |
21 |
22 |
23 | )
24 |
25 | export default ErrorPage
26 |
--------------------------------------------------------------------------------
/src/components/Carousel.jsx:
--------------------------------------------------------------------------------
1 | import Img from "gatsby-image"
2 | import PropTypes from "prop-types"
3 | import React from "react"
4 | import Swiper from "react-id-swiper"
5 |
6 | import "swiper/css/swiper.css"
7 | import "./Carousel.css"
8 |
9 | export const Carousel = ({ images }) => {
10 | const swiperParams = {
11 | pagination: {
12 | el: ".swiper-pagination",
13 | type: "bullets",
14 | clickable: true,
15 | },
16 | }
17 | return (
18 |
19 | {images.map(image => {
20 | return (
21 |
22 |
26 |
27 | )
28 | })}
29 |
30 | )
31 | }
32 |
33 | Carousel.propTypes = {
34 | images: PropTypes.arrayOf(PropTypes.object).isRequired,
35 | }
36 |
37 | export default Carousel
38 |
--------------------------------------------------------------------------------
/src/components/Button.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types"
2 | import React from "react"
3 |
4 | const Button = ({ children, href, ...params }) => {
5 | const className =
6 | "inline-block px-5 py-3 font-medium leading-snug border border-transparent text-base rounded-md text-white bg-blue-700 hover:bg-blue-600 focus:outline-none focus:shadow-outline transition duration-150 ease-in-out"
7 |
8 | if (href) {
9 | return (
10 |
16 | {children}
17 |
18 | )
19 | } else {
20 | return (
21 |
22 | {children}
23 |
24 | )
25 | }
26 | }
27 |
28 | Button.propTypes = {
29 | children: PropTypes.node.isRequired,
30 | href: PropTypes.string,
31 | }
32 |
33 | Button.defaultProps = {
34 | href: null,
35 | }
36 |
37 | export default Button
38 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import { graphql } from "gatsby"
2 | import React from "react"
3 | import Cards from "../components/Cards"
4 | import Hero from "../components/Hero"
5 | import Layout from "../layouts/Layout"
6 | import Newsletter from "../components/Newsletter"
7 | import SiteMetadata from "../components/SiteMetadata"
8 |
9 | const IndexPage = ({ data }) => {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 | {data.portfolio && data.portfolio.nodes.length > 0 ? (
18 |
19 | ) : (
20 |
No projects found.
21 | )}
22 |
23 |
24 |
25 | )
26 | }
27 |
28 | export default IndexPage
29 |
30 | export const query = graphql`
31 | query HomeQuery {
32 | portfolio: allContentfulPortfolio {
33 | nodes {
34 | ...PortfolioCard
35 | }
36 | }
37 | }
38 | `
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Wojciech Kocjan
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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # dotenv environment variable files
55 | .env
56 |
57 | # gatsby files
58 | .cache/
59 | public
60 |
61 | # Mac files
62 | .DS_Store
63 |
64 | # Yarn
65 | yarn-error.log
66 | .pnp/
67 | .pnp.js
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
--------------------------------------------------------------------------------
/gatsby-node.js:
--------------------------------------------------------------------------------
1 | const path = require(`path`)
2 |
3 | exports.createSchemaCustomization = ({ actions }) => {
4 | const { createTypes } = actions
5 | const typeDefs = `
6 | type contentfulPortfolioDescriptionTextNode implements Node {
7 | description: String
8 | }
9 | type ContentfulPortfolio implements Node {
10 | description: contentfulPortfolioDescriptionTextNode
11 | gallery: [ContentfulAsset]
12 | id: ID!
13 | name: String!
14 | related: [ContentfulPortfolio]
15 | slug: String!
16 | summary: String!
17 | thumbnail: ContentfulAsset
18 | url: String
19 | }
20 | `
21 | createTypes(typeDefs)
22 | }
23 |
24 | exports.createPages = ({ graphql, actions }) => {
25 | const { createPage } = actions
26 |
27 | return new Promise((resolve, reject) => {
28 | graphql(`
29 | {
30 | portfolio: allContentfulPortfolio {
31 | nodes {
32 | slug
33 | }
34 | }
35 | }
36 | `).then(({ errors, data }) => {
37 | if (errors) {
38 | reject(errors)
39 | }
40 |
41 | if (data && data.portfolio) {
42 | const component = path.resolve("./src/templates/portfolio-item.jsx")
43 | data.portfolio.nodes.map(({ slug }) => {
44 | createPage({
45 | path: `/${slug}`,
46 | component,
47 | context: { slug },
48 | })
49 | })
50 | }
51 |
52 | resolve()
53 | })
54 | })
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/Card.jsx:
--------------------------------------------------------------------------------
1 | import Img from "gatsby-image"
2 | import { graphql, Link } from "gatsby"
3 | import PropTypes from "prop-types"
4 | import React from "react"
5 |
6 | const Card = props => {
7 | const { name, slug, summary, thumbnail } = props
8 |
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
{name}
17 |
{summary}
18 |
19 |
20 |
21 | )
22 | }
23 |
24 | Card.propTypes = {
25 | name: PropTypes.string.isRequired,
26 | slug: PropTypes.string.isRequired,
27 | summary: PropTypes.string.isRequired,
28 | thumbnail: PropTypes.shape({
29 | localFile: PropTypes.object,
30 | }),
31 | }
32 |
33 | export default Card
34 |
35 | export const query = graphql`
36 | fragment PortfolioCard on ContentfulPortfolio {
37 | id
38 | name
39 | slug
40 | thumbnail {
41 | localFile {
42 | childImageSharp {
43 | fluid(maxWidth: 444, maxHeight: 342, quality: 85) {
44 | ...GatsbyImageSharpFluid_withWebp
45 | }
46 | }
47 | }
48 | }
49 | summary
50 | }
51 | `
52 |
--------------------------------------------------------------------------------
/src/components/SiteMetadata.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Helmet } from "react-helmet"
3 | import { graphql, useStaticQuery } from "gatsby"
4 | import PropTypes from "prop-types"
5 |
6 | const SiteMetadata = ({ title, description, image }) => {
7 | const {
8 | site: {
9 | siteMetadata: { locale, siteTitle },
10 | },
11 | } = useStaticQuery(graphql`
12 | query SiteMetadata {
13 | site {
14 | siteMetadata {
15 | locale
16 | siteTitle: title
17 | }
18 | }
19 | }
20 | `)
21 |
22 | return (
23 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | )
42 | }
43 |
44 | SiteMetadata.propTypes = {
45 | title: PropTypes.string.isRequired,
46 | description: PropTypes.string.isRequired,
47 | image: PropTypes.string,
48 | }
49 |
50 | SiteMetadata.defaultProps = {
51 | image: "/social.png",
52 | }
53 |
54 | export default SiteMetadata
55 |
--------------------------------------------------------------------------------
/src/components/MenuMobile.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "gatsby"
2 | import { motion } from "framer-motion"
3 | import PropTypes from "prop-types"
4 | import React from "react"
5 | import Overlay from "./Overlay"
6 |
7 | const menuItem = {
8 | closed: {
9 | opacity: 0,
10 | transition: {
11 | delay: 0,
12 | duration: 0,
13 | },
14 | x: -20,
15 | },
16 | open: key => ({
17 | opacity: 1,
18 | transition: {
19 | delay: 0.25 + key * 0.1,
20 | type: "tween",
21 | },
22 | x: 0,
23 | }),
24 | }
25 |
26 | const MenuMobile = ({ links, isOpen, setIsOpen }) => {
27 | return (
28 |
29 |
30 |
31 | {links.map((link, key) => (
32 |
39 | setIsOpen(false)}
44 | >
45 | {link.name}
46 |
47 |
48 | ))}
49 |
50 |
51 |
52 | )
53 | }
54 |
55 | MenuMobile.propTypes = {
56 | links: PropTypes.arrayOf(
57 | PropTypes.shape({
58 | name: PropTypes.string.isRequired,
59 | to: PropTypes.string.isRequired,
60 | })
61 | ),
62 | isOpen: PropTypes.bool.isRequired,
63 | setIsOpen: PropTypes.func.isRequired,
64 | }
65 |
66 | export default MenuMobile
67 |
--------------------------------------------------------------------------------
/gatsby-config.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config()
2 |
3 | const { CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN } = process.env
4 |
5 | if (!CONTENTFUL_SPACE_ID || !CONTENTFUL_ACCESS_TOKEN) {
6 | throw new Error(
7 | "Contentful spaceId and the access token need to be provided."
8 | )
9 | }
10 |
11 | module.exports = {
12 | siteMetadata: {
13 | menu: [
14 | { name: "Home", to: "/" },
15 | { name: "About", to: "/about" },
16 | ],
17 | links: {
18 | facebook: "https://www.facebook.com/",
19 | instagram: "https://www.instagram.com/",
20 | pinterest: "https://pinterest.com/",
21 | twitter: "https://twitter.com/",
22 | },
23 | locale: "en",
24 | title: `John Doe`,
25 | description: `Photography portfolio of John Doe`,
26 | author: `@johndoe`,
27 | },
28 | plugins: [
29 | `gatsby-plugin-postcss`,
30 | `gatsby-plugin-react-helmet`,
31 | {
32 | resolve: `gatsby-source-contentful`,
33 | options: {
34 | spaceId: CONTENTFUL_SPACE_ID,
35 | accessToken: CONTENTFUL_ACCESS_TOKEN,
36 | downloadLocal: true,
37 | },
38 | },
39 | {
40 | resolve: `gatsby-source-filesystem`,
41 | options: {
42 | name: `images`,
43 | path: `${__dirname}/src/images`,
44 | },
45 | },
46 | `gatsby-transformer-sharp`,
47 | `gatsby-plugin-sharp`,
48 | {
49 | resolve: "gatsby-plugin-mailchimp",
50 | options: {
51 | endpoint: process.env.MAILCHIMP_ENDPOINT,
52 | },
53 | },
54 | {
55 | resolve: `gatsby-plugin-manifest`,
56 | options: {
57 | name: `John Doe`,
58 | short_name: `johndoe`,
59 | start_url: `/`,
60 | background_color: `#ffffff`,
61 | theme_color: `#3182ce`,
62 | display: `minimal-ui`,
63 | icon: `src/images/icon.png`,
64 | },
65 | },
66 | ],
67 | }
68 |
--------------------------------------------------------------------------------
/src/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import { graphql, Link, useStaticQuery } from "gatsby"
2 | import React, { useState } from "react"
3 | import MenuMobile from "./MenuMobile"
4 | import { FaBars } from "react-icons/fa"
5 |
6 | const Header = () => {
7 | const [isMenuOpen, setIsMenuOpen] = useState(false)
8 |
9 | const { site } = useStaticQuery(graphql`
10 | query {
11 | site {
12 | data: siteMetadata {
13 | menu {
14 | name
15 | to
16 | }
17 | }
18 | }
19 | }
20 | `)
21 |
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
setIsMenuOpen(true)}
32 | aria-label="Open Menu"
33 | >
34 |
35 |
36 |
37 |
38 | {site.data.menu.map((link, key) => (
39 |
45 | {link.name}
46 |
47 | ))}
48 |
49 |
50 |
55 |
56 | )
57 | }
58 |
59 | export default Header
60 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-contentful-portfolio",
3 | "description": "Portfolio theme for GatsbyJS",
4 | "version": "1.0.0",
5 | "author": "Wojciech Kocjan ",
6 | "dependencies": {
7 | "autoprefixer": "^9.7.6",
8 | "classnames": "^2.2.6",
9 | "dotenv": "^8.2.0",
10 | "framer-motion": "^1.10.3",
11 | "gatsby": "^2.21.10",
12 | "gatsby-image": "^2.4.0",
13 | "gatsby-plugin-mailchimp": "^5.1.2",
14 | "gatsby-plugin-manifest": "^2.4.1",
15 | "gatsby-plugin-postcss": "^2.3.0",
16 | "gatsby-plugin-react-helmet": "^3.3.0",
17 | "gatsby-plugin-sharp": "^2.6.0",
18 | "gatsby-source-contentful": "^2.3.1",
19 | "gatsby-source-filesystem": "^2.3.0",
20 | "gatsby-transformer-sharp": "^2.5.0",
21 | "prop-types": "^15.7.2",
22 | "react": "^16.13.1",
23 | "react-dom": "^16.13.1",
24 | "react-helmet": "^5.2.1",
25 | "react-icons": "^3.10.0",
26 | "react-id-swiper": "^3.0.0",
27 | "swiper": "^5.3.8",
28 | "tailwindcss": "^1.4.4",
29 | "typeface-inter": "^3.12.0"
30 | },
31 | "devDependencies": {
32 | "chalk": "^3.0.0",
33 | "contentful-import": "^7.7.13",
34 | "inquirer": "^7.1.0",
35 | "prettier": "^1.19.1"
36 | },
37 | "keywords": [
38 | "gatsby",
39 | "portfolio",
40 | "contentful"
41 | ],
42 | "license": "MIT",
43 | "scripts": {
44 | "build": "gatsby build",
45 | "develop": "gatsby develop",
46 | "format": "prettier --write \"**/*.{js,jsx,json,md}\"",
47 | "postinstall": "node ./bin/hello.js",
48 | "start": "npm run develop",
49 | "serve": "gatsby serve",
50 | "setup": "node ./bin/setup.js",
51 | "clean": "gatsby clean"
52 | },
53 | "repository": {
54 | "type": "git",
55 | "url": "https://github.com/wkocjan/gatsby-contentful-portfolio"
56 | },
57 | "bugs": {
58 | "url": "https://github.com/wkocjan/gatsby-contentful-portfolio/issues"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import { graphql, useStaticQuery } from "gatsby"
2 | import React from "react"
3 | import { FaFacebook, FaInstagram, FaTwitter, FaPinterest } from "react-icons/fa"
4 |
5 | const Footer = () => {
6 | const {
7 | site: {
8 | meta: { links },
9 | },
10 | } = useStaticQuery(graphql`
11 | query FooterQuery {
12 | site {
13 | meta: siteMetadata {
14 | links {
15 | facebook
16 | instagram
17 | pinterest
18 | twitter
19 | }
20 | }
21 | }
22 | }
23 | `)
24 |
25 | return (
26 |
27 |
41 |
42 |
43 | © 2020 John Doe. All rights reserved.
44 |
45 |
46 |
47 | )
48 | }
49 |
50 | const FooterLink = ({ href, label, icon: Icon }) => {
51 | return (
52 |
53 |
59 | {label}
60 |
61 |
62 |
63 | )
64 | }
65 |
66 | export default Footer
67 |
--------------------------------------------------------------------------------
/src/components/Newsletter.jsx:
--------------------------------------------------------------------------------
1 | import classNames from "classnames"
2 | import addToMailchimp from "gatsby-plugin-mailchimp"
3 | import React, { useState } from "react"
4 | import Button from "../components/Button"
5 |
6 | const Newsletter = () => {
7 | const [email, setEmail] = useState()
8 | const [message, setMessage] = useState()
9 | const [disabled, setDisabled] = useState(false)
10 |
11 | const handleSubmit = async event => {
12 | event.preventDefault()
13 | setDisabled(true)
14 | setMessage("Sending...")
15 | const response = await addToMailchimp(email)
16 | if (response.result === "error") {
17 | if (response.msg.toLowerCase().includes("already subscribed")) {
18 | setMessage("You're already on to the list!")
19 | } else {
20 | setMessage("Some error occured while subscribing you to the list.")
21 | }
22 | setDisabled(false)
23 | } else {
24 | setMessage(
25 | "Thanks! Please check your e-mail and click the confirmation link."
26 | )
27 | }
28 | }
29 |
30 | return (
31 |
32 |
33 | Sign up for my newsletter
34 |
35 |
48 |
51 | {message}
52 |
53 |
54 | )
55 | }
56 |
57 | export default Newsletter
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Portfolio theme for Gatsby
2 |
3 | ### Gatsby starter theme integrated with [Contentful](https://www.contentful.com)
4 |
5 | ## Demo
6 |
7 | Live demo is available at:
8 | https://gatsby-contentful-portfolio.netlify.com/
9 |
10 | ## Screenshot
11 |
12 | 
13 |
14 | ## Who is this for?
15 |
16 | - Graphic designers
17 | - Photographers
18 | - Illustrators
19 | - Other creatives
20 |
21 | ## Features
22 |
23 | - Integration with [Contentful](https://www.contentful.com) - automated content model & demo setup
24 | - Responsive/adaptive images via [gatsby-image](https://www.gatsbyjs.org/packages/gatsby-image/)
25 | - Uses utility-first [TailwindCSS](https://tailwindcss.com/) framework
26 | - Integration with [Mailchimp](https://mailchimp.com/) - newsletter form
27 | - Responsive design (desktop / mobile)
28 |
29 | ## Getting started
30 |
31 | Install [Node package manager (NPM)](https://nodejs.org/) (if you haven't already).
32 |
33 | ## Requirements
34 |
35 | To use this project you have to have a Contentful account. If you don't have one yet you can register at [www.contentful.com/sign-up](https://www.contentful.com/sign-up/).
36 |
37 | ### Get the source code and install dependencies.
38 |
39 | ```
40 | $ git clone git@github.com:wkocjan/gatsby-contentful-portfolio.git
41 | $ npm install
42 | ```
43 |
44 | ### Set up of the needed content model and create a configuration file
45 |
46 | This project comes with a Contentful setup command `npm run setup`.
47 |
48 | This command will ask you for a space ID, and access tokens for the Contentful Management and Delivery API and then import the needed content model into the space you define and write a config file (`.env`).
49 |
50 | ### Set up Mailchimp
51 |
52 | If you want to use built-in integration with Mailchimp, please provide your unique endpoind URL in the `.env` file (`MAILCHIMP_ENDPOINT` variable).
53 |
54 | Follow [this instruction](https://www.gatsbyjs.org/packages/gatsby-plugin-mailchimp/?=mailchimp#mailchimp-endpoint) to get the endpoint value.
55 |
56 | ## Crucial Commands
57 |
58 | This project comes with a few handy commands for linting and code fixing. The most important ones are the ones to develop and ship code. You can find the most important commands below.
59 |
60 | #### `gatsby develop`
61 |
62 | Run in the project locally.
63 |
64 | #### `gatsby build`
65 |
66 | Run a production build into `./public`. The result is ready to be put on any static hosting you prefer.
67 |
--------------------------------------------------------------------------------
/src/pages/about.js:
--------------------------------------------------------------------------------
1 | import { graphql } from "gatsby"
2 | import Img from "gatsby-image"
3 | import React from "react"
4 | import Layout from "../layouts/Layout"
5 | import Newsletter from "../components/Newsletter"
6 | import SiteMetadata from "../components/SiteMetadata"
7 |
8 | const AboutPage = ({ data }) => (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | About me
18 |
19 |
20 |
21 | Interdum et malesuada fames ac ante.
22 |
23 |
24 | Curabitur non hendrerit dolor. Interdum et malesuada fames ac ante
25 | ipsum primis in faucibus. Ut sapien ex, fringilla sed
26 | consectetur et, pharetra eget lacus.
27 |
28 |
29 | Morbi sem leo, varius ut tempus et, tempor sit amet nibh.
30 | Curabitur fermentum feugiat libero, sed egestas lorem aliquam et.
31 | Praesent id mi purus. Morbi sem leo, varius ut tempus et, tempor
32 | sit amet nibh.
33 |
34 |
35 | I'm happy to hear from you:
36 |
37 |
41 | contact@johndoe.com
42 |
43 |
44 |
45 |
46 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | )
58 |
59 | export default AboutPage
60 |
61 | export const query = graphql`
62 | query {
63 | author: file(relativePath: { eq: "author.jpg" }) {
64 | childImageSharp {
65 | fluid(maxWidth: 600, maxHeight: 480, quality: 85) {
66 | ...GatsbyImageSharpFluid_withWebp
67 | }
68 | }
69 | }
70 | }
71 | `
72 |
--------------------------------------------------------------------------------
/src/components/Overlay.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion"
2 | import PropTypes from "prop-types"
3 | import React, { useEffect } from "react"
4 | import { FaTimes } from "react-icons/fa"
5 |
6 | const backgroundVariants = {
7 | closed: {
8 | opacity: 0,
9 | transitionEnd: { display: "none" },
10 | },
11 | open: {
12 | bottom: 0,
13 | display: "block",
14 | left: 0,
15 | opacity: 1,
16 | right: 0,
17 | top: 0,
18 | },
19 | }
20 |
21 | const closeButtonVariants = {
22 | closed: {
23 | opacity: 0,
24 | },
25 | open: {
26 | opacity: 1,
27 | transition: {
28 | delay: 0.75,
29 | duration: 0.5,
30 | },
31 | },
32 | }
33 |
34 | const childrenVariants = {
35 | closed: {
36 | opacity: 0,
37 | },
38 | open: {
39 | opacity: 1,
40 | transition: {
41 | delay: 0.25,
42 | },
43 | },
44 | }
45 |
46 | function Overlay({ children, isOpen, setIsOpen }) {
47 | function closeOnEscapeKey(event) {
48 | if (event.keyCode === 27 && isOpen) {
49 | setIsOpen(false)
50 | }
51 | }
52 |
53 | useEffect(() => {
54 | window.addEventListener("keydown", closeOnEscapeKey)
55 | return () => {
56 | window.removeEventListener("keydown", closeOnEscapeKey)
57 | }
58 | })
59 |
60 | useEffect(() => {
61 | document
62 | .querySelectorAll("body, html")
63 | .forEach(e => e.classList[isOpen ? "add" : "remove"]("overflow-hidden"))
64 | }, [isOpen])
65 |
66 | return (
67 |
73 |
74 |
75 | setIsOpen(false)}
81 | >
82 |
83 |
84 |
85 |
90 | {children}
91 |
92 |
93 |
94 | )
95 | }
96 |
97 | Overlay.propTypes = {
98 | children: PropTypes.node.isRequired,
99 | isOpen: PropTypes.bool.isRequired,
100 | setIsOpen: PropTypes.func.isRequired,
101 | }
102 |
103 | export default Overlay
104 |
--------------------------------------------------------------------------------
/bin/setup.js:
--------------------------------------------------------------------------------
1 | const spaceImport = require("contentful-import")
2 | const exportFile = require("../contentful/export.json")
3 | const inquirer = require("inquirer")
4 | const chalk = require("chalk")
5 | const path = require("path")
6 | const { writeFileSync } = require("fs")
7 |
8 | const argv = require("yargs-parser")(process.argv.slice(2))
9 |
10 | console.log(`
11 | To set up this project you need to provide your Space ID
12 | and the belonging API access tokens.
13 |
14 | You can find all the needed information in your Contentful space under:
15 |
16 | ${chalk.yellow(
17 | `app.contentful.com ${chalk.red("->")} Space Settings ${chalk.red(
18 | "->"
19 | )} API keys`
20 | )}
21 |
22 | The ${chalk.green("Content Management API Token")}
23 | will be used to import and write data to your space.
24 |
25 | The ${chalk.green("Content Delivery API Token")}
26 | will be used to ship published production-ready content in your Gatsby app.
27 |
28 | Ready? Let's do it! 🎉
29 | `)
30 |
31 | const questions = [
32 | {
33 | name: "spaceId",
34 | message: "Your Space ID",
35 | when: !argv.spaceId && !process.env.CONTENTFUL_SPACE_ID,
36 | validate: input =>
37 | /^[a-z0-9]{12}$/.test(input) ||
38 | "Space ID must be 12 lowercase characters",
39 | },
40 | {
41 | name: "managementToken",
42 | when: !argv.managementToken,
43 | message: "Your Content Management API access token",
44 | },
45 | {
46 | name: "accessToken",
47 | when: !argv.accessToken && !process.env.CONTENTFUL_ACCESS_TOKEN,
48 | message: "Your Content Delivery API access token",
49 | },
50 | ]
51 |
52 | inquirer
53 | .prompt(questions)
54 | .then(({ spaceId, managementToken, accessToken }) => {
55 | const { CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN } = process.env
56 |
57 | // env vars are given precedence followed by args provided to the setup
58 | // followed by input given to prompts displayed by the setup script
59 | spaceId = CONTENTFUL_SPACE_ID || argv.spaceId || spaceId
60 | managementToken = argv.managementToken || managementToken
61 | accessToken = CONTENTFUL_ACCESS_TOKEN || argv.accessToken || accessToken
62 |
63 | console.log("Writing config file...")
64 | const configFiles = [`.env`].map(file => path.join(__dirname, "..", file))
65 |
66 | const fileContents =
67 | [
68 | `# Do NOT commit this file to source control`,
69 | `CONTENTFUL_SPACE_ID='${spaceId}'`,
70 | `CONTENTFUL_ACCESS_TOKEN='${accessToken}'`,
71 | ``,
72 | `# https://www.gatsbyjs.org/packages/gatsby-plugin-mailchimp/?=mailchimp#mailchimp-endpoint`,
73 | `MAILCHIMP_ENDPOINT='https://example.us10.list-manage.com/subscribe/post?u=123'`,
74 | ].join("\n") + "\n"
75 |
76 | configFiles.forEach(file => {
77 | writeFileSync(file, fileContents, "utf8")
78 | console.log(`Config file ${chalk.yellow(file)} written`)
79 | })
80 | return { spaceId, managementToken }
81 | })
82 | .then(({ spaceId, managementToken }) => {
83 | spaceImport({ spaceId, managementToken, content: exportFile })
84 | })
85 | .catch(error => console.error(error))
86 |
--------------------------------------------------------------------------------
/static/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/templates/portfolio-item.jsx:
--------------------------------------------------------------------------------
1 | import { graphql } from "gatsby"
2 | import Img from "gatsby-image"
3 | import React from "react"
4 | import SiteMetadata from "../components/SiteMetadata"
5 | import Button from "../components/Button"
6 | import Cards from "../components/Cards"
7 | import Carousel from "../components/Carousel"
8 | import Newsletter from "../components/Newsletter"
9 | import Layout from "../layouts/Layout"
10 |
11 | export default props => {
12 | const {
13 | description,
14 | gallery,
15 | name,
16 | related,
17 | summary,
18 | thumbnail,
19 | url,
20 | } = props.data.item
21 |
22 | return (
23 |
24 |
29 |
30 |
31 |
32 |
33 | {gallery && gallery.length === 1 && (
34 |
38 | )}
39 | {gallery && gallery.length > 1 &&
}
40 |
41 |
42 |
43 | {name}
44 |
45 |
46 | {summary}
47 |
48 | {description && (
49 |
50 | {description.description}
51 |
52 | )}
53 | {url && (
54 |
55 | More info
56 |
57 | )}
58 |
59 |
60 |
61 |
62 | {related && (
63 |
64 |
65 |
66 | You may also like
67 |
68 |
69 |
70 |
71 | )}
72 |
73 |
74 | )
75 | }
76 |
77 | export const query = graphql`
78 | query PortfolioItemQUery($slug: String!) {
79 | item: contentfulPortfolio(slug: { eq: $slug }) {
80 | description {
81 | description
82 | }
83 | gallery {
84 | id
85 | localFile {
86 | childImageSharp {
87 | fluid(maxWidth: 960, quality: 85) {
88 | ...GatsbyImageSharpFluid_withWebp
89 | }
90 | }
91 | }
92 | title
93 | }
94 | name
95 | related {
96 | ...PortfolioCard
97 | }
98 | summary
99 | thumbnail {
100 | localFile {
101 | publicURL
102 | }
103 | }
104 | url
105 | }
106 | }
107 | `
108 |
--------------------------------------------------------------------------------
/contentful/export.json:
--------------------------------------------------------------------------------
1 | {
2 | "contentTypes": [
3 | {
4 | "sys": {
5 | "space": {
6 | "sys": {
7 | "type": "Link",
8 | "linkType": "Space",
9 | "id": "uy3h42sj0upk"
10 | }
11 | },
12 | "id": "portfolio",
13 | "type": "ContentType",
14 | "createdAt": "2020-03-11T21:38:58.382Z",
15 | "updatedAt": "2020-03-11T21:52:30.325Z",
16 | "environment": {
17 | "sys": {
18 | "id": "master",
19 | "type": "Link",
20 | "linkType": "Environment"
21 | }
22 | },
23 | "publishedVersion": 9,
24 | "publishedAt": "2020-03-11T21:52:30.325Z",
25 | "firstPublishedAt": "2020-03-11T21:38:58.972Z",
26 | "createdBy": {
27 | "sys": {
28 | "type": "Link",
29 | "linkType": "User",
30 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
31 | }
32 | },
33 | "updatedBy": {
34 | "sys": {
35 | "type": "Link",
36 | "linkType": "User",
37 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
38 | }
39 | },
40 | "publishedCounter": 5,
41 | "version": 10,
42 | "publishedBy": {
43 | "sys": {
44 | "type": "Link",
45 | "linkType": "User",
46 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
47 | }
48 | }
49 | },
50 | "displayField": "name",
51 | "name": "Portfolio",
52 | "description": "",
53 | "fields": [
54 | {
55 | "id": "name",
56 | "name": "Name",
57 | "type": "Symbol",
58 | "localized": false,
59 | "required": true,
60 | "validations": [
61 | ],
62 | "disabled": false,
63 | "omitted": false
64 | },
65 | {
66 | "id": "summary",
67 | "name": "Summary",
68 | "type": "Symbol",
69 | "localized": false,
70 | "required": true,
71 | "validations": [
72 | {
73 | "size": {
74 | "max": 80
75 | }
76 | }
77 | ],
78 | "disabled": false,
79 | "omitted": false
80 | },
81 | {
82 | "id": "slug",
83 | "name": "Slug",
84 | "type": "Symbol",
85 | "localized": false,
86 | "required": true,
87 | "validations": [
88 | {
89 | "unique": true
90 | }
91 | ],
92 | "disabled": false,
93 | "omitted": false
94 | },
95 | {
96 | "id": "thumbnail",
97 | "name": "Thumbnail",
98 | "type": "Link",
99 | "localized": false,
100 | "required": true,
101 | "validations": [
102 | {
103 | "linkMimetypeGroup": [
104 | "image"
105 | ]
106 | }
107 | ],
108 | "disabled": false,
109 | "omitted": false,
110 | "linkType": "Asset"
111 | },
112 | {
113 | "id": "description",
114 | "name": "Description",
115 | "type": "Text",
116 | "localized": false,
117 | "required": false,
118 | "validations": [
119 | ],
120 | "disabled": false,
121 | "omitted": false
122 | },
123 | {
124 | "id": "gallery",
125 | "name": "Gallery",
126 | "type": "Array",
127 | "localized": false,
128 | "required": false,
129 | "validations": [
130 | ],
131 | "disabled": false,
132 | "omitted": false,
133 | "items": {
134 | "type": "Link",
135 | "validations": [
136 | {
137 | "linkMimetypeGroup": [
138 | "image"
139 | ]
140 | },
141 | {
142 | "assetImageDimensions": {
143 | "width": {
144 | "min": 900,
145 | "max": null
146 | },
147 | "height": {
148 | "min": 600,
149 | "max": null
150 | }
151 | }
152 | }
153 | ],
154 | "linkType": "Asset"
155 | }
156 | },
157 | {
158 | "id": "url",
159 | "name": "External URL",
160 | "type": "Symbol",
161 | "localized": false,
162 | "required": false,
163 | "validations": [
164 | ],
165 | "disabled": false,
166 | "omitted": false
167 | },
168 | {
169 | "id": "related",
170 | "name": "Related projects",
171 | "type": "Array",
172 | "localized": false,
173 | "required": false,
174 | "validations": [
175 | {
176 | "size": {
177 | "min": 0,
178 | "max": 3
179 | }
180 | }
181 | ],
182 | "disabled": false,
183 | "omitted": false,
184 | "items": {
185 | "type": "Link",
186 | "validations": [
187 | {
188 | "linkContentType": [
189 | "portfolio"
190 | ]
191 | }
192 | ],
193 | "linkType": "Entry"
194 | }
195 | }
196 | ]
197 | }
198 | ],
199 | "editorInterfaces": [
200 | {
201 | "sys": {
202 | "id": "default",
203 | "type": "EditorInterface",
204 | "space": {
205 | "sys": {
206 | "id": "uy3h42sj0upk",
207 | "type": "Link",
208 | "linkType": "Space"
209 | }
210 | },
211 | "version": 10,
212 | "createdAt": "2020-03-11T21:38:59.048Z",
213 | "createdBy": {
214 | "sys": {
215 | "id": "5GBl2yAHzXnu8cqPwGEcMy",
216 | "type": "Link",
217 | "linkType": "User"
218 | }
219 | },
220 | "updatedAt": "2020-03-11T21:52:30.777Z",
221 | "updatedBy": {
222 | "sys": {
223 | "id": "5GBl2yAHzXnu8cqPwGEcMy",
224 | "type": "Link",
225 | "linkType": "User"
226 | }
227 | },
228 | "contentType": {
229 | "sys": {
230 | "id": "portfolio",
231 | "type": "Link",
232 | "linkType": "ContentType"
233 | }
234 | },
235 | "environment": {
236 | "sys": {
237 | "id": "master",
238 | "type": "Link",
239 | "linkType": "Environment"
240 | }
241 | }
242 | },
243 | "controls": [
244 | {
245 | "fieldId": "name",
246 | "widgetId": "singleLine",
247 | "widgetNamespace": "builtin"
248 | },
249 | {
250 | "fieldId": "summary",
251 | "widgetId": "singleLine",
252 | "widgetNamespace": "builtin"
253 | },
254 | {
255 | "fieldId": "slug",
256 | "widgetId": "slugEditor",
257 | "widgetNamespace": "builtin"
258 | },
259 | {
260 | "fieldId": "thumbnail",
261 | "widgetId": "assetLinkEditor",
262 | "widgetNamespace": "builtin"
263 | },
264 | {
265 | "fieldId": "description",
266 | "widgetId": "multipleLine",
267 | "widgetNamespace": "builtin"
268 | },
269 | {
270 | "fieldId": "gallery",
271 | "widgetId": "assetGalleryEditor",
272 | "widgetNamespace": "builtin"
273 | },
274 | {
275 | "fieldId": "url",
276 | "widgetId": "urlEditor",
277 | "widgetNamespace": "builtin"
278 | },
279 | {
280 | "fieldId": "related",
281 | "settings": {
282 | "bulkEditing": false
283 | },
284 | "widgetId": "entryCardsEditor",
285 | "widgetNamespace": "builtin"
286 | }
287 | ]
288 | }
289 | ],
290 | "entries": [
291 | {
292 | "sys": {
293 | "space": {
294 | "sys": {
295 | "type": "Link",
296 | "linkType": "Space",
297 | "id": "uy3h42sj0upk"
298 | }
299 | },
300 | "id": "6ybuKsRJxC4IGL1FwuAzen",
301 | "type": "Entry",
302 | "createdAt": "2020-03-11T21:43:30.668Z",
303 | "updatedAt": "2020-03-12T13:55:44.326Z",
304 | "environment": {
305 | "sys": {
306 | "id": "master",
307 | "type": "Link",
308 | "linkType": "Environment"
309 | }
310 | },
311 | "publishedVersion": 44,
312 | "publishedAt": "2020-03-12T13:55:44.326Z",
313 | "firstPublishedAt": "2020-03-11T21:47:52.191Z",
314 | "createdBy": {
315 | "sys": {
316 | "type": "Link",
317 | "linkType": "User",
318 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
319 | }
320 | },
321 | "updatedBy": {
322 | "sys": {
323 | "type": "Link",
324 | "linkType": "User",
325 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
326 | }
327 | },
328 | "publishedCounter": 8,
329 | "version": 45,
330 | "publishedBy": {
331 | "sys": {
332 | "type": "Link",
333 | "linkType": "User",
334 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
335 | }
336 | },
337 | "contentType": {
338 | "sys": {
339 | "type": "Link",
340 | "linkType": "ContentType",
341 | "id": "portfolio"
342 | }
343 | }
344 | },
345 | "fields": {
346 | "name": {
347 | "en-US": "Iceland"
348 | },
349 | "summary": {
350 | "en-US": "Photos from my trip to Iceland"
351 | },
352 | "slug": {
353 | "en-US": "iceland"
354 | },
355 | "thumbnail": {
356 | "en-US": {
357 | "sys": {
358 | "type": "Link",
359 | "linkType": "Asset",
360 | "id": "2rXXfCq7OmHHL4rXDp4uMl"
361 | }
362 | }
363 | },
364 | "description": {
365 | "en-US": "Aliquam erat volutpat. Pellentesque at neque diam. Pellentesque hendrerit, odio a pellentesque blandit, tellus nisl tempor tellus, placerat consequat ipsum ante finibus augue.\n\nUt porttitor nibh vitae elementum pretium. Morbi varius pulvinar purus, non tempus sem facilisis quis. Vivamus ac vulputate metus, ut fringilla erat."
366 | },
367 | "gallery": {
368 | "en-US": [
369 | {
370 | "sys": {
371 | "type": "Link",
372 | "linkType": "Asset",
373 | "id": "2rXXfCq7OmHHL4rXDp4uMl"
374 | }
375 | },
376 | {
377 | "sys": {
378 | "type": "Link",
379 | "linkType": "Asset",
380 | "id": "2hf317UJY21LgAalyf7kgf"
381 | }
382 | },
383 | {
384 | "sys": {
385 | "type": "Link",
386 | "linkType": "Asset",
387 | "id": "YsoKW4AhvPFCKvux2GnzK"
388 | }
389 | }
390 | ]
391 | },
392 | "url": {
393 | "en-US": "https://www.iceland.is"
394 | },
395 | "related": {
396 | "en-US": [
397 | {
398 | "sys": {
399 | "type": "Link",
400 | "linkType": "Entry",
401 | "id": "6wJlM7wIdJ6rg55tWt8x2c"
402 | }
403 | },
404 | {
405 | "sys": {
406 | "type": "Link",
407 | "linkType": "Entry",
408 | "id": "cGMSGyWttmfv1RVTaITE3"
409 | }
410 | }
411 | ]
412 | }
413 | }
414 | },
415 | {
416 | "sys": {
417 | "space": {
418 | "sys": {
419 | "type": "Link",
420 | "linkType": "Space",
421 | "id": "uy3h42sj0upk"
422 | }
423 | },
424 | "id": "6wJlM7wIdJ6rg55tWt8x2c",
425 | "type": "Entry",
426 | "createdAt": "2020-03-11T21:53:36.798Z",
427 | "updatedAt": "2020-03-12T15:14:43.100Z",
428 | "environment": {
429 | "sys": {
430 | "id": "master",
431 | "type": "Link",
432 | "linkType": "Environment"
433 | }
434 | },
435 | "publishedVersion": 77,
436 | "publishedAt": "2020-03-12T15:14:43.100Z",
437 | "firstPublishedAt": "2020-03-11T21:54:21.908Z",
438 | "createdBy": {
439 | "sys": {
440 | "type": "Link",
441 | "linkType": "User",
442 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
443 | }
444 | },
445 | "updatedBy": {
446 | "sys": {
447 | "type": "Link",
448 | "linkType": "User",
449 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
450 | }
451 | },
452 | "publishedCounter": 10,
453 | "version": 78,
454 | "publishedBy": {
455 | "sys": {
456 | "type": "Link",
457 | "linkType": "User",
458 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
459 | }
460 | },
461 | "contentType": {
462 | "sys": {
463 | "type": "Link",
464 | "linkType": "ContentType",
465 | "id": "portfolio"
466 | }
467 | }
468 | },
469 | "fields": {
470 | "name": {
471 | "en-US": "Spain"
472 | },
473 | "summary": {
474 | "en-US": "Photographies from my trip to Spain"
475 | },
476 | "slug": {
477 | "en-US": "spain"
478 | },
479 | "thumbnail": {
480 | "en-US": {
481 | "sys": {
482 | "type": "Link",
483 | "linkType": "Asset",
484 | "id": "4Iqv52VORq3RHI1JUb6KxQ"
485 | }
486 | }
487 | },
488 | "description": {
489 | "en-US": "Duis posuere porta odio, et finibus ex dapibus at. Quisque condimentum turpis eget ligula congue, ac convallis tortor porta.\n\nEtiam vulputate odio nec ex viverra, a tristique ante tristique. Sed at scelerisque metus. "
490 | },
491 | "gallery": {
492 | "en-US": [
493 | {
494 | "sys": {
495 | "type": "Link",
496 | "linkType": "Asset",
497 | "id": "4PwvtBmFDzNSlYyGOuJCBg"
498 | }
499 | },
500 | {
501 | "sys": {
502 | "type": "Link",
503 | "linkType": "Asset",
504 | "id": "4Iqv52VORq3RHI1JUb6KxQ"
505 | }
506 | }
507 | ]
508 | },
509 | "url": {
510 | "en-US": "https://www.spain.info"
511 | },
512 | "related": {
513 | "en-US": [
514 | {
515 | "sys": {
516 | "type": "Link",
517 | "linkType": "Entry",
518 | "id": "6ybuKsRJxC4IGL1FwuAzen"
519 | }
520 | },
521 | {
522 | "sys": {
523 | "type": "Link",
524 | "linkType": "Entry",
525 | "id": "cGMSGyWttmfv1RVTaITE3"
526 | }
527 | }
528 | ]
529 | }
530 | }
531 | },
532 | {
533 | "sys": {
534 | "space": {
535 | "sys": {
536 | "type": "Link",
537 | "linkType": "Space",
538 | "id": "uy3h42sj0upk"
539 | }
540 | },
541 | "id": "cGMSGyWttmfv1RVTaITE3",
542 | "type": "Entry",
543 | "createdAt": "2020-03-12T13:46:17.514Z",
544 | "updatedAt": "2020-03-12T13:55:54.656Z",
545 | "environment": {
546 | "sys": {
547 | "id": "master",
548 | "type": "Link",
549 | "linkType": "Environment"
550 | }
551 | },
552 | "publishedVersion": 26,
553 | "publishedAt": "2020-03-12T13:55:54.656Z",
554 | "firstPublishedAt": "2020-03-12T13:53:43.180Z",
555 | "createdBy": {
556 | "sys": {
557 | "type": "Link",
558 | "linkType": "User",
559 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
560 | }
561 | },
562 | "updatedBy": {
563 | "sys": {
564 | "type": "Link",
565 | "linkType": "User",
566 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
567 | }
568 | },
569 | "publishedCounter": 2,
570 | "version": 27,
571 | "publishedBy": {
572 | "sys": {
573 | "type": "Link",
574 | "linkType": "User",
575 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
576 | }
577 | },
578 | "contentType": {
579 | "sys": {
580 | "type": "Link",
581 | "linkType": "ContentType",
582 | "id": "portfolio"
583 | }
584 | }
585 | },
586 | "fields": {
587 | "name": {
588 | "en-US": "Poland"
589 | },
590 | "summary": {
591 | "en-US": "Photos from trip to Poland, 2019"
592 | },
593 | "slug": {
594 | "en-US": "poland"
595 | },
596 | "thumbnail": {
597 | "en-US": {
598 | "sys": {
599 | "type": "Link",
600 | "linkType": "Asset",
601 | "id": "2pzRh27gIiOAHXkonEritC"
602 | }
603 | }
604 | },
605 | "description": {
606 | "en-US": "Vestibulum at ipsum non massa viverra accumsan. Praesent id iaculis ex. Mauris nec tortor eros. Nunc in sapien quis nibh congue maximus nec sagittis nunc.\n\nQuisque pretium elit ut ante tincidunt, a interdum neque lobortis. "
607 | },
608 | "gallery": {
609 | "en-US": [
610 | {
611 | "sys": {
612 | "type": "Link",
613 | "linkType": "Asset",
614 | "id": "2pzRh27gIiOAHXkonEritC"
615 | }
616 | },
617 | {
618 | "sys": {
619 | "type": "Link",
620 | "linkType": "Asset",
621 | "id": "7lDS5OCqQiJI6JWQwfooTy"
622 | }
623 | }
624 | ]
625 | },
626 | "url": {
627 | "en-US": "https://www.polska.pl/"
628 | },
629 | "related": {
630 | "en-US": [
631 | {
632 | "sys": {
633 | "type": "Link",
634 | "linkType": "Entry",
635 | "id": "6wJlM7wIdJ6rg55tWt8x2c"
636 | }
637 | },
638 | {
639 | "sys": {
640 | "type": "Link",
641 | "linkType": "Entry",
642 | "id": "6ybuKsRJxC4IGL1FwuAzen"
643 | }
644 | }
645 | ]
646 | }
647 | }
648 | }
649 | ],
650 | "assets": [
651 | {
652 | "sys": {
653 | "space": {
654 | "sys": {
655 | "type": "Link",
656 | "linkType": "Space",
657 | "id": "uy3h42sj0upk"
658 | }
659 | },
660 | "id": "2rXXfCq7OmHHL4rXDp4uMl",
661 | "type": "Asset",
662 | "createdAt": "2020-03-11T21:43:54.667Z",
663 | "updatedAt": "2020-03-11T21:44:21.147Z",
664 | "environment": {
665 | "sys": {
666 | "id": "master",
667 | "type": "Link",
668 | "linkType": "Environment"
669 | }
670 | },
671 | "publishedVersion": 7,
672 | "publishedAt": "2020-03-11T21:44:21.147Z",
673 | "firstPublishedAt": "2020-03-11T21:44:21.147Z",
674 | "createdBy": {
675 | "sys": {
676 | "type": "Link",
677 | "linkType": "User",
678 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
679 | }
680 | },
681 | "updatedBy": {
682 | "sys": {
683 | "type": "Link",
684 | "linkType": "User",
685 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
686 | }
687 | },
688 | "publishedCounter": 1,
689 | "version": 8,
690 | "publishedBy": {
691 | "sys": {
692 | "type": "Link",
693 | "linkType": "User",
694 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
695 | }
696 | }
697 | },
698 | "fields": {
699 | "title": {
700 | "en-US": "Iceland"
701 | },
702 | "file": {
703 | "en-US": {
704 | "url": "//images.ctfassets.net/uy3h42sj0upk/2rXXfCq7OmHHL4rXDp4uMl/391516cf47d399b741aa23c371b295ff/iceland01.jpg",
705 | "details": {
706 | "size": 127727,
707 | "image": {
708 | "width": 900,
709 | "height": 600
710 | }
711 | },
712 | "fileName": "iceland01.jpg",
713 | "contentType": "image/jpeg"
714 | }
715 | }
716 | }
717 | },
718 | {
719 | "sys": {
720 | "space": {
721 | "sys": {
722 | "type": "Link",
723 | "linkType": "Space",
724 | "id": "uy3h42sj0upk"
725 | }
726 | },
727 | "id": "2hf317UJY21LgAalyf7kgf",
728 | "type": "Asset",
729 | "createdAt": "2020-03-11T21:44:33.468Z",
730 | "updatedAt": "2020-03-11T21:44:54.320Z",
731 | "environment": {
732 | "sys": {
733 | "id": "master",
734 | "type": "Link",
735 | "linkType": "Environment"
736 | }
737 | },
738 | "publishedVersion": 5,
739 | "publishedAt": "2020-03-11T21:44:54.320Z",
740 | "firstPublishedAt": "2020-03-11T21:44:54.320Z",
741 | "createdBy": {
742 | "sys": {
743 | "type": "Link",
744 | "linkType": "User",
745 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
746 | }
747 | },
748 | "updatedBy": {
749 | "sys": {
750 | "type": "Link",
751 | "linkType": "User",
752 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
753 | }
754 | },
755 | "publishedCounter": 1,
756 | "version": 6,
757 | "publishedBy": {
758 | "sys": {
759 | "type": "Link",
760 | "linkType": "User",
761 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
762 | }
763 | }
764 | },
765 | "fields": {
766 | "title": {
767 | "en-US": "Iceland"
768 | },
769 | "file": {
770 | "en-US": {
771 | "url": "//images.ctfassets.net/uy3h42sj0upk/2hf317UJY21LgAalyf7kgf/489c6ba17772f4bc5fd60397482de054/iceland02.jpg",
772 | "details": {
773 | "size": 84809,
774 | "image": {
775 | "width": 900,
776 | "height": 600
777 | }
778 | },
779 | "fileName": "iceland02.jpg",
780 | "contentType": "image/jpeg"
781 | }
782 | }
783 | }
784 | },
785 | {
786 | "sys": {
787 | "space": {
788 | "sys": {
789 | "type": "Link",
790 | "linkType": "Space",
791 | "id": "uy3h42sj0upk"
792 | }
793 | },
794 | "id": "YsoKW4AhvPFCKvux2GnzK",
795 | "type": "Asset",
796 | "createdAt": "2020-03-11T21:45:05.020Z",
797 | "updatedAt": "2020-03-11T21:45:16.321Z",
798 | "environment": {
799 | "sys": {
800 | "id": "master",
801 | "type": "Link",
802 | "linkType": "Environment"
803 | }
804 | },
805 | "publishedVersion": 7,
806 | "publishedAt": "2020-03-11T21:45:16.321Z",
807 | "firstPublishedAt": "2020-03-11T21:45:16.321Z",
808 | "createdBy": {
809 | "sys": {
810 | "type": "Link",
811 | "linkType": "User",
812 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
813 | }
814 | },
815 | "updatedBy": {
816 | "sys": {
817 | "type": "Link",
818 | "linkType": "User",
819 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
820 | }
821 | },
822 | "publishedCounter": 1,
823 | "version": 8,
824 | "publishedBy": {
825 | "sys": {
826 | "type": "Link",
827 | "linkType": "User",
828 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
829 | }
830 | }
831 | },
832 | "fields": {
833 | "title": {
834 | "en-US": "Iceland"
835 | },
836 | "file": {
837 | "en-US": {
838 | "url": "//images.ctfassets.net/uy3h42sj0upk/YsoKW4AhvPFCKvux2GnzK/a0423854b24dda06a727df23457680d9/iceland03.jpg",
839 | "details": {
840 | "size": 126788,
841 | "image": {
842 | "width": 900,
843 | "height": 600
844 | }
845 | },
846 | "fileName": "iceland03.jpg",
847 | "contentType": "image/jpeg"
848 | }
849 | }
850 | }
851 | },
852 | {
853 | "sys": {
854 | "space": {
855 | "sys": {
856 | "type": "Link",
857 | "linkType": "Space",
858 | "id": "uy3h42sj0upk"
859 | }
860 | },
861 | "id": "4PwvtBmFDzNSlYyGOuJCBg",
862 | "type": "Asset",
863 | "createdAt": "2020-03-12T13:42:58.901Z",
864 | "updatedAt": "2020-03-12T13:43:20.769Z",
865 | "environment": {
866 | "sys": {
867 | "id": "master",
868 | "type": "Link",
869 | "linkType": "Environment"
870 | }
871 | },
872 | "publishedVersion": 5,
873 | "publishedAt": "2020-03-12T13:43:20.769Z",
874 | "firstPublishedAt": "2020-03-12T13:43:20.769Z",
875 | "createdBy": {
876 | "sys": {
877 | "type": "Link",
878 | "linkType": "User",
879 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
880 | }
881 | },
882 | "updatedBy": {
883 | "sys": {
884 | "type": "Link",
885 | "linkType": "User",
886 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
887 | }
888 | },
889 | "publishedCounter": 1,
890 | "version": 6,
891 | "publishedBy": {
892 | "sys": {
893 | "type": "Link",
894 | "linkType": "User",
895 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
896 | }
897 | }
898 | },
899 | "fields": {
900 | "title": {
901 | "en-US": "Spain"
902 | },
903 | "file": {
904 | "en-US": {
905 | "url": "//images.ctfassets.net/uy3h42sj0upk/4PwvtBmFDzNSlYyGOuJCBg/de2ef5e5ee7156d28ed9b038ac9383b2/spain1.jpg",
906 | "details": {
907 | "size": 163889,
908 | "image": {
909 | "width": 1024,
910 | "height": 684
911 | }
912 | },
913 | "fileName": "spain1.jpg",
914 | "contentType": "image/jpeg"
915 | }
916 | }
917 | }
918 | },
919 | {
920 | "sys": {
921 | "space": {
922 | "sys": {
923 | "type": "Link",
924 | "linkType": "Space",
925 | "id": "uy3h42sj0upk"
926 | }
927 | },
928 | "id": "4Iqv52VORq3RHI1JUb6KxQ",
929 | "type": "Asset",
930 | "createdAt": "2020-03-12T13:43:37.075Z",
931 | "updatedAt": "2020-03-12T13:43:50.319Z",
932 | "environment": {
933 | "sys": {
934 | "id": "master",
935 | "type": "Link",
936 | "linkType": "Environment"
937 | }
938 | },
939 | "publishedVersion": 5,
940 | "publishedAt": "2020-03-12T13:43:50.319Z",
941 | "firstPublishedAt": "2020-03-12T13:43:50.319Z",
942 | "createdBy": {
943 | "sys": {
944 | "type": "Link",
945 | "linkType": "User",
946 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
947 | }
948 | },
949 | "updatedBy": {
950 | "sys": {
951 | "type": "Link",
952 | "linkType": "User",
953 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
954 | }
955 | },
956 | "publishedCounter": 1,
957 | "version": 6,
958 | "publishedBy": {
959 | "sys": {
960 | "type": "Link",
961 | "linkType": "User",
962 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
963 | }
964 | }
965 | },
966 | "fields": {
967 | "title": {
968 | "en-US": "Spain"
969 | },
970 | "file": {
971 | "en-US": {
972 | "url": "//images.ctfassets.net/uy3h42sj0upk/4Iqv52VORq3RHI1JUb6KxQ/0726f56b5cd387f859579069bc1bb55d/spain2.jpg",
973 | "details": {
974 | "size": 111508,
975 | "image": {
976 | "width": 1024,
977 | "height": 683
978 | }
979 | },
980 | "fileName": "spain2.jpg",
981 | "contentType": "image/jpeg"
982 | }
983 | }
984 | }
985 | },
986 | {
987 | "sys": {
988 | "space": {
989 | "sys": {
990 | "type": "Link",
991 | "linkType": "Space",
992 | "id": "uy3h42sj0upk"
993 | }
994 | },
995 | "id": "2pzRh27gIiOAHXkonEritC",
996 | "type": "Asset",
997 | "createdAt": "2020-03-12T13:52:59.387Z",
998 | "updatedAt": "2020-03-12T13:53:14.656Z",
999 | "environment": {
1000 | "sys": {
1001 | "id": "master",
1002 | "type": "Link",
1003 | "linkType": "Environment"
1004 | }
1005 | },
1006 | "publishedVersion": 5,
1007 | "publishedAt": "2020-03-12T13:53:14.656Z",
1008 | "firstPublishedAt": "2020-03-12T13:53:14.656Z",
1009 | "createdBy": {
1010 | "sys": {
1011 | "type": "Link",
1012 | "linkType": "User",
1013 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
1014 | }
1015 | },
1016 | "updatedBy": {
1017 | "sys": {
1018 | "type": "Link",
1019 | "linkType": "User",
1020 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
1021 | }
1022 | },
1023 | "publishedCounter": 1,
1024 | "version": 6,
1025 | "publishedBy": {
1026 | "sys": {
1027 | "type": "Link",
1028 | "linkType": "User",
1029 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
1030 | }
1031 | }
1032 | },
1033 | "fields": {
1034 | "title": {
1035 | "en-US": "Poland"
1036 | },
1037 | "file": {
1038 | "en-US": {
1039 | "url": "//images.ctfassets.net/uy3h42sj0upk/2pzRh27gIiOAHXkonEritC/008f0035cc2b9d129b8382c5a26b82f3/poland1.jpg",
1040 | "details": {
1041 | "size": 156784,
1042 | "image": {
1043 | "width": 1024,
1044 | "height": 681
1045 | }
1046 | },
1047 | "fileName": "poland1.jpg",
1048 | "contentType": "image/jpeg"
1049 | }
1050 | }
1051 | }
1052 | },
1053 | {
1054 | "sys": {
1055 | "space": {
1056 | "sys": {
1057 | "type": "Link",
1058 | "linkType": "Space",
1059 | "id": "uy3h42sj0upk"
1060 | }
1061 | },
1062 | "id": "7lDS5OCqQiJI6JWQwfooTy",
1063 | "type": "Asset",
1064 | "createdAt": "2020-03-12T13:53:24.071Z",
1065 | "updatedAt": "2020-03-12T13:53:39.922Z",
1066 | "environment": {
1067 | "sys": {
1068 | "id": "master",
1069 | "type": "Link",
1070 | "linkType": "Environment"
1071 | }
1072 | },
1073 | "publishedVersion": 5,
1074 | "publishedAt": "2020-03-12T13:53:39.922Z",
1075 | "firstPublishedAt": "2020-03-12T13:53:39.922Z",
1076 | "createdBy": {
1077 | "sys": {
1078 | "type": "Link",
1079 | "linkType": "User",
1080 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
1081 | }
1082 | },
1083 | "updatedBy": {
1084 | "sys": {
1085 | "type": "Link",
1086 | "linkType": "User",
1087 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
1088 | }
1089 | },
1090 | "publishedCounter": 1,
1091 | "version": 6,
1092 | "publishedBy": {
1093 | "sys": {
1094 | "type": "Link",
1095 | "linkType": "User",
1096 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
1097 | }
1098 | }
1099 | },
1100 | "fields": {
1101 | "title": {
1102 | "en-US": "Poland"
1103 | },
1104 | "file": {
1105 | "en-US": {
1106 | "url": "//images.ctfassets.net/uy3h42sj0upk/7lDS5OCqQiJI6JWQwfooTy/bcdc1841ddb5690a1478a12fd26ef58a/poland2.jpg",
1107 | "details": {
1108 | "size": 165085,
1109 | "image": {
1110 | "width": 1024,
1111 | "height": 683
1112 | }
1113 | },
1114 | "fileName": "poland2.jpg",
1115 | "contentType": "image/jpeg"
1116 | }
1117 | }
1118 | }
1119 | }
1120 | ],
1121 | "locales": [
1122 | {
1123 | "name": "English (United States)",
1124 | "code": "en-US",
1125 | "fallbackCode": null,
1126 | "default": true,
1127 | "contentManagementApi": true,
1128 | "contentDeliveryApi": true,
1129 | "optional": false,
1130 | "sys": {
1131 | "type": "Locale",
1132 | "id": "4p7VjiICdBr5pbo5mRt2b3",
1133 | "version": 1,
1134 | "space": {
1135 | "sys": {
1136 | "type": "Link",
1137 | "linkType": "Space",
1138 | "id": "uy3h42sj0upk"
1139 | }
1140 | },
1141 | "environment": {
1142 | "sys": {
1143 | "type": "Link",
1144 | "linkType": "Environment",
1145 | "id": "master"
1146 | }
1147 | },
1148 | "createdBy": {
1149 | "sys": {
1150 | "type": "Link",
1151 | "linkType": "User",
1152 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
1153 | }
1154 | },
1155 | "createdAt": "2020-03-11T21:31:17Z",
1156 | "updatedBy": {
1157 | "sys": {
1158 | "type": "Link",
1159 | "linkType": "User",
1160 | "id": "5GBl2yAHzXnu8cqPwGEcMy"
1161 | }
1162 | },
1163 | "updatedAt": "2020-03-11T21:31:17Z"
1164 | }
1165 | }
1166 | ],
1167 | "webhooks": [
1168 | ],
1169 | "roles": [
1170 | ]
1171 | }
--------------------------------------------------------------------------------