├── packages └── demo │ ├── src │ ├── components │ │ ├── Cart │ │ │ └── components │ │ │ │ ├── CartContents.css.js │ │ │ │ ├── cart.graphql │ │ │ │ ├── addToCart.graphql │ │ │ │ ├── Currency.js │ │ │ │ ├── ShippingAddress.js │ │ │ │ ├── media.css.js │ │ │ │ ├── SubmitButton.js │ │ │ │ ├── CheckoutProceedButton.js │ │ │ │ ├── EmailButton.js │ │ │ │ ├── CartSide.jsx │ │ │ │ ├── icons │ │ │ │ └── Cart.js │ │ │ │ ├── AddToCart.js │ │ │ │ ├── styles.css.js │ │ │ │ ├── CartItem.jsx │ │ │ │ ├── CartContents.js │ │ │ │ ├── Cart.css.js │ │ │ │ ├── Checkout.js │ │ │ │ ├── Totals.js │ │ │ │ ├── CartItemQuantity.js │ │ │ │ ├── Cart.js │ │ │ │ └── AddressForm.js │ │ ├── ProductImage │ │ │ ├── index.js │ │ │ ├── imageQuery.graphql │ │ │ └── ProductImage.js │ │ ├── ProductOptions │ │ │ ├── index.js │ │ │ ├── .ported │ │ │ ├── tileList.js │ │ │ ├── swatchList.js │ │ │ ├── options.js │ │ │ ├── tile.js │ │ │ ├── option.js │ │ │ └── swatch.js │ │ ├── Search │ │ │ ├── .ported │ │ │ ├── SuggestedProducts.js │ │ │ ├── SuggestedCategories.js │ │ │ ├── SuggestedProduct.js │ │ │ ├── SearchInput.js │ │ │ └── SearchAutocomplete.js │ │ ├── EcommercePlatform │ │ │ ├── index.js │ │ │ ├── EcommercePlatform.js │ │ │ └── CatalogQuery.jsx │ │ ├── header │ │ │ ├── nav │ │ │ │ ├── nav.css.js │ │ │ │ ├── nav.js │ │ │ │ ├── icons │ │ │ │ │ └── Home.js │ │ │ │ └── Hamburger.js │ │ │ ├── header.css.js │ │ │ └── header.js │ │ ├── Checkout │ │ │ └── Steps │ │ │ │ ├── components │ │ │ │ ├── StepContext.js │ │ │ │ ├── StepAnimator.js │ │ │ │ ├── StepRenderer.js │ │ │ │ ├── ShippingMethod.js │ │ │ │ ├── Shipping.js │ │ │ │ ├── Step.css.js │ │ │ │ ├── Step.js │ │ │ │ ├── CustomerStep.js │ │ │ │ └── PaymentForm.jsx │ │ │ │ └── index.js │ │ ├── CmsBlock │ │ │ └── ProductsList.jsx │ │ ├── Price.js │ │ ├── Catalog │ │ │ ├── components │ │ │ │ ├── Pagination │ │ │ │ │ ├── utils.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── Tiles.js │ │ │ │ │ └── Controls.js │ │ │ │ ├── ProductName.js │ │ │ │ └── Item.js │ │ │ └── index.js │ │ ├── CategoryTree │ │ │ ├── DataProvider.js │ │ │ ├── TreeView.js │ │ │ ├── Drawer.js │ │ │ └── index.js │ │ ├── transition │ │ │ └── transition.js │ │ ├── title │ │ │ └── title.js │ │ ├── CategoryPage │ │ │ ├── components │ │ │ │ ├── BreadCrumbs.jsx │ │ │ │ └── SubCategories.jsx │ │ │ └── index.jsx │ │ ├── Layout.js │ │ ├── CmsBlockRenderer.jsx │ │ ├── Account │ │ │ ├── LoginForm.js │ │ │ ├── EmailForm.js │ │ │ └── AccountForm.js │ │ └── ProductDetails │ │ │ └── index.js │ ├── images │ │ └── icon.png │ ├── pages │ │ ├── checkoutConfig.graphql │ │ ├── 404.jsx │ │ ├── customers │ │ │ └── account │ │ │ │ ├── login.js │ │ │ │ └── create.js │ │ ├── productDetails.graphql │ │ ├── catalogsearch │ │ │ └── result.js │ │ ├── index.jsx │ │ ├── checkout │ │ │ └── success.jsx │ │ ├── product.jsx │ │ ├── checkout.jsx │ │ ├── category.jsx │ │ ├── product.dev.jsx │ │ └── category.dev.jsx │ ├── layout │ │ ├── widths.js │ │ ├── Responsive.js │ │ ├── Animate.js │ │ └── Box.js │ ├── platform │ │ ├── magento │ │ │ ├── catalogContext.js │ │ │ ├── transform.js │ │ │ └── MagentoPlatformContext.js │ │ ├── queries.js │ │ ├── index.js │ │ ├── fragments.js │ │ ├── client.js │ │ ├── schema.graphql │ │ └── mutations.js │ ├── helpers │ │ ├── wrapPageElement.js │ │ ├── Fela.js │ │ ├── schemaGenerator.js │ │ └── formComponents.js │ ├── utils │ │ └── react.js │ ├── client.js │ └── client.gatsby.js │ ├── static │ ├── robots.txt │ └── social.png │ ├── content │ └── catalog │ │ └── category.jpg │ ├── flow-typed │ ├── storeFront.js │ └── magentoApi.js │ ├── .prettierrc │ ├── .gitignore │ ├── site-config.js │ ├── fela.config.js │ ├── .eslintrc │ ├── README.md │ ├── traefik.toml │ ├── graphql.config.json │ ├── gatsby-browser.js │ ├── gatsby-ssr.js │ ├── fela.theme.js │ ├── gatsby-config.js │ └── package.json ├── .gitignore ├── README.md └── package.json /packages/demo/src/components/Cart/components/CartContents.css.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/demo/src/components/ProductImage/index.js: -------------------------------------------------------------------------------- 1 | export default from './ProductImage'; 2 | -------------------------------------------------------------------------------- /packages/demo/src/components/ProductOptions/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './options'; 2 | -------------------------------------------------------------------------------- /packages/demo/src/components/Search/.ported: -------------------------------------------------------------------------------- 1 | These components were ported from Magento PWA Studio. -------------------------------------------------------------------------------- /packages/demo/src/components/EcommercePlatform/index.js: -------------------------------------------------------------------------------- 1 | export default from './EcommercePlatform.js'; 2 | -------------------------------------------------------------------------------- /packages/demo/src/components/ProductOptions/.ported: -------------------------------------------------------------------------------- 1 | These components were ported from Magento PWA Studio. -------------------------------------------------------------------------------- /packages/demo/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | Sitemap: https://gu.fabianschultz.com/sitemap.xml 4 | -------------------------------------------------------------------------------- /packages/demo/src/components/Cart/components/cart.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | total { 3 | itemsQty 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/demo/static/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neostorefront/storefront/HEAD/packages/demo/static/social.png -------------------------------------------------------------------------------- /packages/demo/src/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neostorefront/storefront/HEAD/packages/demo/src/images/icon.png -------------------------------------------------------------------------------- /packages/demo/content/catalog/category.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neostorefront/storefront/HEAD/packages/demo/content/catalog/category.jpg -------------------------------------------------------------------------------- /packages/demo/src/components/Cart/components/addToCart.graphql: -------------------------------------------------------------------------------- 1 | mutation addItem($item: CartItem!) { 2 | addCartItem(item: $item) @client 3 | } 4 | -------------------------------------------------------------------------------- /packages/demo/src/pages/checkoutConfig.graphql: -------------------------------------------------------------------------------- 1 | { 2 | checkoutConfig(id: 1) { 3 | braintree 4 | braintree_paypal 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/demo/src/layout/widths.js: -------------------------------------------------------------------------------- 1 | const widths = { 2 | phone: '100%', 3 | tablet: '90vw', 4 | desktop: '1000px', 5 | }; 6 | 7 | export default widths; 8 | -------------------------------------------------------------------------------- /packages/demo/flow-typed/storeFront.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type CartItem = { 3 | id: number, 4 | sku: string, 5 | name: string, 6 | qty: number, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/demo/src/platform/magento/catalogContext.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export default { 4 | items: [], 5 | setItems: (items: Object[]) => { 6 | console.log('items:', items); 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /packages/demo/flow-typed/magentoApi.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type MagentoCartItem = { 3 | item_id: number, 4 | sku: string, 5 | name: string, 6 | qty: number, 7 | price: number, 8 | 9 | }; 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | .env 4 | 5 | .idea 6 | 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | .cache/ 15 | public 16 | build 17 | 18 | .idea/ 19 | -------------------------------------------------------------------------------- /packages/demo/src/components/header/nav/nav.css.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Container = styled.nav` 4 | margin: 0 auto; 5 | text-align: center; 6 | `; 7 | /* 8 | 9 | */ 10 | -------------------------------------------------------------------------------- /packages/demo/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "bracketSpacing": true, 6 | "jsxBracketSameLine": false, 7 | "parser": "babylon", 8 | "tabWidth": 4, 9 | "semi": true 10 | } 11 | -------------------------------------------------------------------------------- /packages/demo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | .env 4 | 5 | .idea 6 | .skip 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | .cache/ 16 | public 17 | build 18 | 19 | .idea/ 20 | 21 | public/Caddyfile 22 | -------------------------------------------------------------------------------- /packages/demo/src/helpers/wrapPageElement.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Transition from 'components/transition'; 3 | 4 | const wrapPageElement = ({ element, props }) => { 5 | return {element}; 6 | }; 7 | 8 | export default wrapPageElement; 9 | -------------------------------------------------------------------------------- /packages/demo/src/pages/404.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Box from 'layout/Box'; 3 | import Layout from 'components/Layout'; 4 | 5 | const NotFound = () => ( 6 | 7 | Not found. 8 | 9 | ); 10 | 11 | export default NotFound; 12 | -------------------------------------------------------------------------------- /packages/demo/src/utils/react.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Children, isValidElement } from 'react'; 3 | 4 | export const mapChildren = (nodes, handler, ctx) => { 5 | let idx = 0; 6 | return Children.map(nodes, child => { 7 | return isValidElement(child) ? handler.call(ctx, child, idx++) : child; 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /packages/demo/src/components/Checkout/Steps/components/StepContext.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { createContext } from 'react'; 3 | 4 | type StepContextType = { 5 | open: (name: string) => void, 6 | }; 7 | 8 | const StepContext = createContext({ 9 | open: () => {}, 10 | }); 11 | 12 | export default StepContext; 13 | -------------------------------------------------------------------------------- /packages/demo/src/components/Cart/components/Currency.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from 'react'; 3 | 4 | const Currency = props => ( 5 | 6 | {props.value.toLocaleString('en-US', { 7 | style: 'currency', 8 | currency: 'USD', 9 | })} 10 | 11 | ); 12 | 13 | export default Currency; 14 | -------------------------------------------------------------------------------- /packages/demo/src/platform/magento/transform.js: -------------------------------------------------------------------------------- 1 | export function transformMagentoFeed(feed) { 2 | return feed.map(item => ({ 3 | id: item.id, 4 | sku: item.sku, 5 | name: item.name, 6 | price: item.price.regularPrice.amount.value, 7 | qty: 1, 8 | url: `/${item.url_key}/`, 9 | image: item.image, 10 | })); 11 | } 12 | -------------------------------------------------------------------------------- /packages/demo/src/components/header/header.css.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | // display: flex; 3 | // justify-content: space-between; 4 | // align-items: center; 5 | 6 | export const Container = styled.header` 7 | a { 8 | transition: color 0.2s ease; 9 | text-decoration: none; 10 | 11 | &:hover { 12 | color: inherit; 13 | } 14 | } 15 | `; 16 | -------------------------------------------------------------------------------- /packages/demo/src/layout/Responsive.js: -------------------------------------------------------------------------------- 1 | import { createComponent } from 'react-fela'; 2 | import { mapValueToMediaQuery } from 'fela-tools'; 3 | 4 | const Responsive = ({ width, widths }) => ({ 5 | height: '100%', 6 | margin: '0 auto', 7 | padding: '0 1em', 8 | width, 9 | extend: [mapValueToMediaQuery(widths, value => ({ width: value }))], 10 | }); 11 | 12 | export default createComponent(Responsive); 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Neo Storefront - pre-generated storefront for Magento 2 based on Gatsby.js and PWA Studio 3 | 4 | To run the demo - make sure you have `traefik` installed, then run: 5 | 6 | 1. `yarn install` 7 | 2. `yarn develop` 8 | 9 | and navigate to http://localhost:8099/ 10 | 11 | To do production build: 12 | 13 | 1. `yarn build` 14 | 2. `yarn serve` 15 | 16 | Production HTML files will be generated in the packages/demo/public/. 17 | -------------------------------------------------------------------------------- /packages/demo/src/components/CmsBlock/ProductsList.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import { transformMagentoFeed } from 'platform/magento/transform'; 4 | import Catalog from 'components/Catalog'; 5 | 6 | /** 7 | * ProductList Properties 8 | */ 9 | type Props = {}; 10 | 11 | const ProductsList = (props: Props) => { 12 | return ; 13 | }; 14 | 15 | export default ProductsList; 16 | -------------------------------------------------------------------------------- /packages/demo/src/components/Cart/components/ShippingAddress.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import Box from 'layout/Box'; 4 | import AddressForm from './AddressForm'; 5 | 6 | /** 7 | * ShippingAddress Properties 8 | */ 9 | type Props = { 10 | onSubmit: address => void, 11 | }; 12 | 13 | /** 14 | */ 15 | const ShippingAddress = ({ onSubmit }: Props) => ( 16 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /packages/demo/src/components/Price.js: -------------------------------------------------------------------------------- 1 | import { FelaComponent, withTheme } from 'react-fela'; 2 | import React from 'react'; 3 | 4 | const Price = withTheme(({ theme, children }) => ( 5 | 12 | {children} 13 | 14 | )); 15 | 16 | export default Price; 17 | -------------------------------------------------------------------------------- /packages/demo/src/components/ProductImage/imageQuery.graphql: -------------------------------------------------------------------------------- 1 | query FetchImageData($sku: String) { 2 | magentoProduct(sku: { eq: $sku }) { 3 | image { 4 | childImageSharp { 5 | fluid(maxWidth: 1024, maxHeight: 1024) { 6 | src 7 | srcSet 8 | sizes 9 | aspectRatio 10 | base64 11 | } 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/demo/src/components/Cart/components/media.css.js: -------------------------------------------------------------------------------- 1 | // TODO use layout instead 2 | const sizes = { 3 | desktop: 992, 4 | tablet: 800, 5 | phone: 576, 6 | }; 7 | 8 | // Iterate through the sizes and create a media template 9 | export const media = Object.keys(sizes).reduce((acc, label) => { 10 | acc[label] = styles => ({ 11 | [`@media (max-width: ${sizes[label] / 16}em)`]: { 12 | ...styles, 13 | }, 14 | }); 15 | 16 | return acc; 17 | }, {}); 18 | -------------------------------------------------------------------------------- /packages/demo/src/components/ProductOptions/tileList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { arrayOf, object, shape, string } from 'prop-types'; 3 | import { List } from '@magento/peregrine'; 4 | 5 | import Tile from './tile'; 6 | 7 | class TileList extends Component { 8 | static propTypes = { 9 | items: arrayOf(object) 10 | }; 11 | 12 | render() { 13 | return ; 14 | } 15 | } 16 | 17 | export default TileList; 18 | -------------------------------------------------------------------------------- /packages/demo/src/components/Cart/components/SubmitButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SimpleButton } from './styles.css'; 3 | 4 | const SubmitButton = props => ( 5 | 16 | ); 17 | 18 | export default SubmitButton; 19 | -------------------------------------------------------------------------------- /packages/demo/src/components/ProductOptions/swatchList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { arrayOf, object, shape, string } from 'prop-types'; 3 | import { List } from '@magento/peregrine'; 4 | 5 | import Swatch from './swatch'; 6 | 7 | class SwatchList extends Component { 8 | static propTypes = { 9 | items: arrayOf(object) 10 | }; 11 | 12 | render() { 13 | return ; 14 | } 15 | } 16 | 17 | export default SwatchList; 18 | -------------------------------------------------------------------------------- /packages/demo/src/pages/customers/account/login.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Box from 'layout/Box'; 3 | import Layout from 'components/Layout'; 4 | import LoginForm from 'components/Account/LoginForm'; 5 | 6 | const Login = props => ( 7 | 8 | 9 | { 11 | console.log('User: ', user); 12 | }} 13 | /> 14 | 15 | 16 | ); 17 | 18 | export default Login; 19 | -------------------------------------------------------------------------------- /packages/demo/site-config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | siteTitle: `Magento Gatsby PWA Demo`, 5 | siteTitleShort: `Magento-Gatsby-PWA`, 6 | siteDescription: `An opinionated starter for Gatsby Magento PWA.`, 7 | siteUrl: `https://mobelop.com/`, 8 | themeColor: `#000`, 9 | backgroundColor: `#fff`, 10 | pathPrefix: null, 11 | logo: path.resolve(__dirname, 'src/images/icon.png'), 12 | social: { 13 | twitter: `gatsbyjs`, 14 | fbAppId: `966242223397117`, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /packages/demo/src/pages/customers/account/create.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Box from 'layout/Box'; 3 | import Layout from 'components/Layout'; 4 | import AccountForm from 'components/Account/AccountForm'; 5 | 6 | const Create = props => ( 7 | 8 | 9 | { 11 | console.log('User: ', user); 12 | }} 13 | /> 14 | 15 | 16 | ); 17 | 18 | export default Create; 19 | -------------------------------------------------------------------------------- /packages/demo/fela.config.js: -------------------------------------------------------------------------------- 1 | import extend from 'fela-plugin-extend' 2 | import namedKeys from 'fela-plugin-named-keys' 3 | 4 | const namedKeysPlugin = namedKeys({ 5 | desktop: '@media (min-width: 1024px)', 6 | tablet: '@media (min-width: 560px) and (max-width: 1024px)', 7 | phone: '@media (min-width: 0px) and (max-width: 560px)', 8 | }) 9 | 10 | export default { 11 | plugins: [ extend(), namedKeysPlugin ], 12 | enhancers: [ /* add your enhancers here */ ], 13 | devMode: process.env.NODE_ENV !== 'production', 14 | // ... 15 | } 16 | -------------------------------------------------------------------------------- /packages/demo/src/components/Catalog/components/Pagination/utils.js: -------------------------------------------------------------------------------- 1 | export const getLeadTile = (currentPage, totalPages, tileBuffer) => { 2 | const selectedTile = currentPage; 3 | const leftBound = 1 + tileBuffer; 4 | const rightBound = totalPages - tileBuffer; 5 | 6 | let leadTile = selectedTile - tileBuffer; 7 | 8 | if (selectedTile < leftBound) { 9 | leadTile = 1; 10 | } else if (selectedTile > rightBound) { 11 | leadTile = Math.max(totalPages - tileBuffer * 2, 1); 12 | } 13 | 14 | return leadTile; 15 | }; 16 | -------------------------------------------------------------------------------- /packages/demo/src/components/Cart/components/CheckoutProceedButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CheckoutButton } from './styles.css'; 3 | 4 | const ProceedButton = props => ( 5 | 17 | ); 18 | 19 | export default ProceedButton; 20 | -------------------------------------------------------------------------------- /packages/demo/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "strict": 0, 9 | "no-console": "warn", 10 | "quotes": [ 11 | "warn", 12 | "single" 13 | ], 14 | "prettier/prettier": "warn" 15 | }, 16 | "extends": [ 17 | "airbnb", 18 | "eslint:recommended", 19 | "plugin:react/recommended", 20 | "plugin:jsx-a11y/recommended", 21 | "plugin:flowtype/recommended", 22 | "prettier", 23 | "prettier/react" 24 | ], 25 | "plugins": ["react", "prettier", "jsx-a11y","import","flowtype"] 26 | } 27 | -------------------------------------------------------------------------------- /packages/demo/src/client.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ApolloClient } from 'apollo-client'; 3 | import { createHttpLink } from 'apollo-link-http'; 4 | import { InMemoryCache } from 'apollo-cache-inmemory'; 5 | 6 | const apiBase = new URL( 7 | '/graphql', 8 | typeof location !== 'undefined' ? location.origin : 'http://localhost/' 9 | ).toString(); 10 | 11 | const httpLink = createHttpLink({ 12 | uri: apiBase, 13 | }); 14 | 15 | const cache = new InMemoryCache(); 16 | 17 | const apolloClient = new ApolloClient({ 18 | link: httpLink, 19 | cache: cache, 20 | }); 21 | 22 | export default apolloClient; 23 | -------------------------------------------------------------------------------- /packages/demo/src/components/Cart/components/EmailButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SimpleButton } from './styles.css'; 3 | 4 | const EmailButton = props => ( 5 | 20 | ); 21 | 22 | export default EmailButton; 23 | -------------------------------------------------------------------------------- /packages/demo/src/components/Catalog/components/ProductName.js: -------------------------------------------------------------------------------- 1 | import { FelaComponent, withTheme } from 'react-fela'; 2 | import React from 'react'; 3 | 4 | const ProductName = withTheme(({ theme, children }) => ( 5 | 17 | {children} 18 | 19 | )); 20 | 21 | export default ProductName; 22 | -------------------------------------------------------------------------------- /packages/demo/src/components/header/nav/nav.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Menu from './menu'; 4 | import Box from 'layout/Box'; 5 | 6 | const menu = [ 7 | { 8 | title: 'Home', 9 | url: '/', 10 | }, 11 | // { 12 | // title: 'Camaro', 13 | // url: '/camaro/', 14 | // }, 15 | // { 16 | // title: 'Dodge', 17 | // url: '/dodge/', 18 | // }, 19 | ]; 20 | 21 | const navStyle = { 22 | background: '#f0f0f0', 23 | }; 24 | 25 | const Nav = () => ( 26 | 27 | 28 | 29 | ); 30 | 31 | export default Nav; 32 | -------------------------------------------------------------------------------- /packages/demo/src/helpers/Fela.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FelaComponent } from 'react-fela'; 3 | import filterInvalidDOMProps from 'filter-invalid-dom-props'; 4 | 5 | const Fela = ({ children, style, as = 'div', ...rest }) => { 6 | const Tag = as; 7 | const htmlProps = filterInvalidDOMProps(rest); 8 | 9 | return ( 10 | 11 | {({ className }) => ( 12 | 13 | {children} 14 | 15 | )} 16 | 17 | ); 18 | }; 19 | 20 | export default Fela; 21 | -------------------------------------------------------------------------------- /packages/demo/src/components/Checkout/Steps/components/StepAnimator.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Animate from 'layout/Animate'; 3 | 4 | const animationName = props => ({ 5 | from: {}, 6 | to: {}, 7 | }); 8 | 9 | const animationNameClose = props => ({ 10 | from: {}, 11 | to: {}, 12 | }); 13 | 14 | const StepAnimator = props => ( 15 | 24 | ); 25 | 26 | export default StepAnimator; 27 | -------------------------------------------------------------------------------- /packages/demo/README.md: -------------------------------------------------------------------------------- 1 | ## Development 2 | 3 | To start development server - run: 4 | 5 | ``` 6 | yarn develop 7 | ``` 8 | 9 | This will start Gatsby on port `8089` and traefik proxy on port `8099`. 10 | 11 | So you should be able to open website on this URL: 12 | 13 | ``` 14 | http://localhost:8099/ 15 | ``` 16 | 17 | ## Architecture Overview 18 | 19 | This project generates a static version of the storefront for the Magento store running on the specified endpoint 20 | (`gatsby-config.js`). 21 | 22 | It communicates with: 23 | 24 | - GraphQL API endpoint 25 | - REST API endpoint 26 | 27 | Run-time communication with Magento is performed via integrated Venia theme (`src/venia`). 28 | 29 | Compile-time communication with Magento is performed via Gatsby plugin. 30 | -------------------------------------------------------------------------------- /packages/demo/src/components/Cart/components/CartSide.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from 'react'; 3 | import { CartView, Drawer } from 'components/Cart/components/Cart.css'; 4 | import CartContents from 'components/Cart/components/Cart'; 5 | import { navigate } from '@reach/router'; 6 | import Totals from './Totals'; 7 | 8 | /** 9 | * CartSide Properties 10 | */ 11 | type Props = {}; 12 | 13 | const CartSide = (props: Props) => ( 14 | this.toggle()}> 15 | 16 | 17 | { 19 | navigate('/checkout'); 20 | }} 21 | /> 22 | 23 | 24 | ); 25 | 26 | export default CartSide; 27 | -------------------------------------------------------------------------------- /packages/demo/src/client.gatsby.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ApolloClient } from 'apollo-client'; 3 | import { createHttpLink } from 'apollo-link-http'; 4 | import { InMemoryCache } from 'apollo-cache-inmemory'; 5 | const cache = new InMemoryCache(); 6 | 7 | let сlientGatsby = null; 8 | 9 | const PRODUCTION = process.env.NODE_ENV === `production`; 10 | 11 | if (typeof window !== 'undefined' && !PRODUCTION) { 12 | const apiBase = new URL( 13 | '/___graphql', 14 | typeof location !== 'undefined' ? location.origin : 'http://localhost/' 15 | ).toString(); 16 | 17 | const httpLink = createHttpLink({ 18 | uri: apiBase, 19 | }); 20 | 21 | сlientGatsby = new ApolloClient({ 22 | link: httpLink, 23 | cache, 24 | }); 25 | } 26 | 27 | export default сlientGatsby; 28 | -------------------------------------------------------------------------------- /packages/demo/traefik.toml: -------------------------------------------------------------------------------- 1 | defaultEntryPoints = ["http"] 2 | 3 | [entryPoints] 4 | [entryPoints.http] 5 | address = ":8099" 6 | 7 | [file] 8 | 9 | [frontends] 10 | [frontends.frontend3] 11 | backend = "backend2" 12 | [frontends.frontend3.routes.test_1] 13 | rule = "Host: localhost; PathPrefix: /graphql" 14 | [frontends.frontend2] 15 | backend = "backend2" 16 | [frontends.frontend2.routes.test_1] 17 | rule = "Host: localhost; PathPrefix: /rest" 18 | [frontends.frontend1] 19 | backend = "backend1" 20 | [frontends.frontend1.routes.test_1] 21 | rule = "Host: localhost" 22 | 23 | [backends] 24 | [backends.backend1] 25 | [backends.backend1.servers.server1] 26 | url = "http://localhost:8089" 27 | [backends.backend2] 28 | [backends.backend2.servers.server1] 29 | url = "https://m23demo.mobelop.com/" 30 | -------------------------------------------------------------------------------- /packages/demo/src/components/EcommercePlatform/EcommercePlatform.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from 'react'; 3 | import { PlatformContext } from 'platform'; 4 | import { ApolloProvider } from 'react-apollo'; 5 | import client from 'platform/client'; 6 | 7 | /** 8 | * EcommercePlatform Properties 9 | */ 10 | type Props = { 11 | children: any, 12 | }; 13 | 14 | /** 15 | * EcommercePlatform State 16 | */ 17 | type State = {}; 18 | 19 | /** 20 | */ 21 | export default class EcommercePlatform extends Component { 22 | static contextType = PlatformContext; 23 | 24 | async componentDidMount(): void { 25 | await this.context.loadCart(); 26 | } 27 | 28 | render() { 29 | const { children } = this.props; 30 | return {children}; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/demo/src/components/header/nav/icons/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const SvgComponent = props => ( 4 | 5 | 9 | 10 | ); 11 | 12 | export default SvgComponent; 13 | -------------------------------------------------------------------------------- /packages/demo/src/platform/queries.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export default { 4 | regions: (obj, { country_id }, { cache }) => { 5 | const query = gql` 6 | query GetCountries { 7 | countries @client { 8 | id 9 | regions { 10 | ... on Region { 11 | id 12 | code 13 | name 14 | } 15 | } 16 | } 17 | } 18 | `; 19 | 20 | const { countries = [] } = cache.readQuery({ query }); 21 | 22 | for (const country of countries) { 23 | if (country.id === country_id) { 24 | return country.regions; 25 | } 26 | } 27 | 28 | return []; 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /packages/demo/graphql.config.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "README_schema" : "Specifies how to load the GraphQL schema that completion, error highlighting, and documentation is based on in the IDE", 4 | "schema": { 5 | "README_request" : "To request the schema from a url instead, remove the 'file' JSON property above (and optionally delete the default graphql.schema.json file).", 6 | "request": { 7 | "url" : "http://localhost:8089/___graphql", 8 | "method" : "POST", 9 | "README_postIntrospectionQuery" : "Whether to POST an introspectionQuery to the url. If the url always returns the schema JSON, set to false and consider using GET", 10 | "postIntrospectionQuery" : true, 11 | "README_options" : "See the 'Options' section at https://github.com/then/then-request", 12 | "options" : { 13 | "headers": { 14 | "user-agent" : "JS GraphQL" 15 | } 16 | } 17 | } 18 | 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "storefront", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "Framework for building pregenerated Magento 2 storefronts based on Gatsby.js and PWA Studio", 6 | "main": "index.js", 7 | "workspaces": [ 8 | "packages/*" 9 | ], 10 | "scripts": { 11 | "develop": "yarn workspaces run develop", 12 | "build": "yarn workspaces run build", 13 | "serve": "yarn workspaces run serve" 14 | }, 15 | "keywords": [ 16 | "magento", 17 | "gatsby.js", 18 | "magento2", 19 | "pwa", 20 | "studio", 21 | "pregenerated", 22 | "static", 23 | "scalability" 24 | ], 25 | "author": "Stanislav Smovdorenko ", 26 | "license": "SEE LICENSE IN LICENSE.txt", 27 | "bugs": { 28 | "url": "https://github.com/neostorefront/storefront/issues" 29 | }, 30 | "homepage": "https://github.com/neostorefront/storefront#readme", 31 | "engines": { 32 | "node": ">=10.7.0", 33 | "yarn": ">=1.12.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/demo/src/components/Checkout/Steps/components/StepRenderer.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from 'react'; 3 | import { mapChildren } from 'utils/react'; 4 | import Step from './Step'; 5 | 6 | /** 7 | * StepRenderer Properties 8 | */ 9 | type Props = { 10 | activeStep: string, 11 | children: React.ChildrenArray>, 12 | }; 13 | 14 | /** 15 | * StepRenderer State 16 | */ 17 | type State = {}; 18 | 19 | /** 20 | */ 21 | export default class StepRenderer extends Component { 22 | renderStep = (child: Step) => { 23 | const { activeStep } = this.props; 24 | const { children, ...rest } = child.props; 25 | return ( 26 | 27 | {children} 28 | 29 | ); 30 | }; 31 | 32 | render() { 33 | const { children } = this.props; 34 | return mapChildren(children, this.renderStep, this); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/demo/src/components/Checkout/Steps/components/ShippingMethod.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import Currency from '../../../Cart/components/Currency'; 3 | import React from 'react'; 4 | 5 | type Props = { 6 | method_title: string, 7 | carrier_title: string, 8 | carrier_code: string, 9 | method_code: string, 10 | total: number, 11 | onSelect: (method: any) => void, 12 | }; 13 | 14 | const ShippingMethod = ({ 15 | method_code, 16 | method_title, 17 | carrier_code, 18 | carrier_title, 19 | total, 20 | onSelect, 21 | }: Props) => ( 22 |
23 | onSelect(carrier_code + '_' + method_code)} 28 | /> 29 | 32 |
33 | ); 34 | 35 | export default ShippingMethod; 36 | -------------------------------------------------------------------------------- /packages/demo/src/platform/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { createContext } from 'react'; 3 | 4 | type CartItem = { 5 | id: number, 6 | }; 7 | 8 | type Platform = { 9 | loadCart(): void, 10 | addItem(item: any): void, 11 | updateItem(item: Object): void, 12 | setShippingAddress(address: Object): void, 13 | setBillingAddress(address: Object): void, 14 | setShippingMethod(method: String): void, 15 | }; 16 | 17 | const notImplemented = () => { 18 | console.error('not implemented!'); 19 | }; 20 | 21 | export const PlatformContext = createContext({ 22 | loadCart: notImplemented, 23 | addItem: notImplemented, 24 | updateItem: notImplemented, 25 | setShippingAddress: notImplemented, 26 | setBillingAddress: notImplemented, 27 | setShippingMethod: notImplemented, 28 | }); 29 | 30 | type Catalog = { 31 | items: Object[], 32 | setItems: (Object[]) => void, 33 | }; 34 | 35 | export const CatalogContext = createContext({ 36 | items: [], 37 | setItems: notImplemented, 38 | }); 39 | -------------------------------------------------------------------------------- /packages/demo/src/components/CategoryTree/DataProvider.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from 'react'; 3 | import { graphql, StaticQuery } from 'gatsby'; 4 | 5 | /** 6 | * DataProvider Properties 7 | */ 8 | type Props = { 9 | render: (data: Object) => any, 10 | }; 11 | 12 | const categoryQuery = graphql` 13 | query CategoryQuery { 14 | allMagentoCategory( 15 | filter: { level: { eq: 2 }, include_in_menu: { eq: 1 } } 16 | ) { 17 | edges { 18 | node { 19 | id 20 | name 21 | include_in_menu 22 | url_key 23 | } 24 | } 25 | } 26 | } 27 | `; 28 | 29 | const DataProvider = ({ render }: Props) => ( 30 | { 33 | return data.allMagentoCategory.edges.map(({ node }) => 34 | render(node) 35 | ); 36 | }} 37 | /> 38 | ); 39 | 40 | export default DataProvider; 41 | -------------------------------------------------------------------------------- /packages/demo/src/components/transition/transition.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import posed, { PoseGroup } from 'react-pose'; 4 | 5 | const timeout = 250; 6 | 7 | class Transition extends PureComponent { 8 | render() { 9 | const { children, location } = this.props; 10 | 11 | const RoutesContainer = posed.div({ 12 | enter: { opacity: 1, delay: timeout, delayChildren: timeout }, 13 | exit: { opacity: 0 }, 14 | }); 15 | 16 | // To enable page transitions on mount / initial load, 17 | // use the prop `animateOnMount={true}` on `PoseGroup`. 18 | return ( 19 | 20 | 21 | {children} 22 | 23 | 24 | ); 25 | } 26 | } 27 | 28 | Transition.propTypes = { 29 | children: PropTypes.node.isRequired, 30 | location: PropTypes.object.isRequired, 31 | }; 32 | 33 | export default Transition; 34 | -------------------------------------------------------------------------------- /packages/demo/src/components/title/title.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from 'react'; 3 | import { FelaComponent, withTheme } from 'react-fela'; 4 | 5 | type Props = { 6 | children?: React.Node, 7 | theme: Object, 8 | fullWidth?: boolean, 9 | extend?: any, 10 | }; 11 | 12 | const Title = ({ 13 | children, 14 | theme, 15 | fullWidth = true, 16 | extend, 17 | ...rest 18 | }: Props) => ( 19 | 38 | {children} 39 | 40 | ); 41 | 42 | export default withTheme(Title); 43 | -------------------------------------------------------------------------------- /packages/demo/gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import wrapPageElementWithTransition from 'helpers/wrapPageElement'; 3 | import { RendererProvider, ThemeProvider } from 'react-fela'; 4 | import { createRenderer } from 'fela'; 5 | import { rehydrate } from 'fela-dom'; 6 | import config from './fela.config.js'; 7 | import theme from './fela.theme.js'; 8 | import globalStyle from 'global.css.js'; 9 | 10 | const isProduction = process.env.NODE_ENV === 'production' 11 | 12 | // React Context in Browser 13 | // eslint-disable-next-line react/prop-types 14 | export const wrapRootElement = ({ element }) => { 15 | const renderer = createRenderer(config); 16 | 17 | if(isProduction) { 18 | rehydrate(renderer) 19 | } else { 20 | renderer.renderStatic(globalStyle) 21 | } 22 | 23 | return ( 24 | 25 | {element} 26 | 27 | ); 28 | }; 29 | 30 | // Page Transitions 31 | export const wrapPageElement = wrapPageElementWithTransition; 32 | -------------------------------------------------------------------------------- /packages/demo/src/components/ProductOptions/options.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { arrayOf, func, shape, string } from 'prop-types'; 3 | 4 | import Option from './option'; 5 | 6 | class Options extends Component { 7 | static propTypes = { 8 | onSelectionChange: func, 9 | options: arrayOf( 10 | shape({ 11 | attribute_id: string.isRequired 12 | }) 13 | ).isRequired 14 | }; 15 | 16 | handleSelectionChange = (optionId, selection) => { 17 | const { onSelectionChange } = this.props; 18 | 19 | if (onSelectionChange) { 20 | onSelectionChange(optionId, selection); 21 | } 22 | }; 23 | 24 | render() { 25 | const { handleSelectionChange, props } = this; 26 | const { options } = props; 27 | 28 | return options.map(option => ( 29 |