├── .babelrc ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── app.json ├── components ├── AddToCart.js ├── CartItemList.js ├── CartSummary.js ├── Header.js ├── Layout.js ├── OrderItemList.js ├── ProductAttributes.js ├── ProductList.js └── ProductSummary.js ├── lib └── moltin.js ├── next.config.js ├── now.json ├── package-lock.json ├── package.json ├── pages ├── cart.js ├── index.js ├── login.js ├── myaccount.js ├── product.js └── register.js └── static ├── moltin-light-hex.svg └── nprogress.css /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], 3 | "env": { 4 | "development": { 5 | "plugins": ["inline-dotenv"] 6 | }, 7 | "production": { 8 | "plugins": ["transform-inline-environment-variables"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | localStorage 4 | 5 | # production 6 | .next 7 | 8 | # misc 9 | .env 10 | .DS_Store 11 | *.log 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 - Present moltin 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js demo store 2 | 3 | > 🛍 Next.js demo store built with moltin 4 | 5 | Next.js webshop built using [Stripe checkout](https://stripe.com/checkout) and [moltin](https://moltin.com). 6 | 7 | Check out the [demo](https://moltin-nextjs-demo-store.now.sh) here. 8 | 9 | ## 🛠 Setup 10 | 11 | Both a moltin and Stripe account are needed for this store to run successfully. 12 | 13 | Create a `.env` file at the project root with your moltin `client_id` and Stripe test `publishable key`. 14 | 15 | ```dosini 16 | MOLTIN_CLIENT_ID= 17 | STRIPE_PUBLISHABLE_KEY= 18 | ``` 19 | 20 | `npm install` 21 | 22 | `npm run dev` 23 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Next.js Demo Store", 3 | "description": "A barebones Next.js store powered by moltin", 4 | "repository": "https://github.com/moltin/nextjs-demo-store", 5 | "logo": "https://www.moltin.com/img/icons/favicons/apple-touch-icon.png", 6 | "keywords": ["moltin", "react", "ecommerce", "next.js", "ssr"], 7 | "success_url": "/", 8 | "env": { 9 | "MOLTIN_CLIENT_ID": { 10 | "description": 11 | "The client_is is the unique ID associated with your store. You can find this inside your moltin Dashboard.", 12 | "value": "EdP3Gi1agyUF3yFS7Ngm8iyodLgbSR3wY4ceoJl0d2" 13 | }, 14 | "STRIPE_KEY": { 15 | "description": 16 | "Your Stripe publishable key. You will need the secret key added to the Moltin Dashboard for Stripe checkout to work correctly." 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /components/AddToCart.js: -------------------------------------------------------------------------------- 1 | import { Input, Button, Label } from 'semantic-ui-react' 2 | 3 | import { addToCart } from '../lib/moltin' 4 | 5 | export default class AddToCart extends React.Component { 6 | state = { 7 | loading: false, 8 | quantity: 1 9 | } 10 | 11 | _handleSubmit = async () => { 12 | const { productId } = this.props 13 | const { quantity } = this.state 14 | const cartId = await localStorage.getItem('mcart') 15 | 16 | this.setState({ 17 | loading: true 18 | }) 19 | 20 | const cart = await addToCart(cartId, productId, quantity) 21 | 22 | this.setState({ 23 | loading: false, 24 | quantity: 1 25 | }) 26 | } 27 | 28 | _handleChange = ({ target: { value } }) => 29 | this.setState({ 30 | quantity: value 31 | }) 32 | 33 | render() { 34 | const { loading, quantity } = this.state 35 | 36 | return ( 37 | this._handleChange(e)} 42 | action={{ 43 | color: 'orange', 44 | content: 'Add to Cart', 45 | icon: 'plus cart', 46 | onClick: this._handleSubmit, 47 | loading, 48 | disabled: loading 49 | }} 50 | /> 51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /components/CartItemList.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import { Item, Button, Loader, Message } from 'semantic-ui-react' 3 | 4 | export default ({ items, removeFromCart, loading, completed }) => { 5 | if (loading) return 6 | 7 | if (completed) 8 | return ( 9 | 10 | Your placed! 11 | Congratulations. Your order and payment has been accepted. 12 | 13 | ) 14 | 15 | if (items.length === 0) 16 | return ( 17 | 18 | Your cart is empty 19 | 20 | You'll need to add some items to the cart before you can checkout. 21 | 22 | 23 | ) 24 | 25 | const mapCartItemsToItems = items => 26 | items.map(({ id, product_id, name, quantity, meta, image }) => { 27 | const price = meta.display_price.with_tax.unit.formatted || '' 28 | const imageUrl = image.href || '/static/moltin-light-hex.svg' 29 | 30 | return { 31 | childKey: id, 32 | header: ( 33 | 34 | {name} 35 | 36 | ), 37 | image: imageUrl, 38 | meta: `${quantity}x ${price}`, 39 | extra: ( 40 | removeFromCart(id)} 45 | /> 46 | ) 47 | } 48 | }) 49 | 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /components/CartSummary.js: -------------------------------------------------------------------------------- 1 | import StripeCheckout from 'react-stripe-checkout' 2 | import { Button, Segment, Divider } from 'semantic-ui-react' 3 | 4 | export default ({ 5 | handleCheckout, 6 | display_price: { 7 | with_tax: { amount, currency, formatted } 8 | } 9 | }) => ( 10 | 11 | 12 | 13 | Sub total: {formatted} 14 | 26 | 27 | Check out 28 | 29 | 30 | 31 | 32 | ) 33 | -------------------------------------------------------------------------------- /components/Header.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import Head from 'next/head' 3 | import Router from 'next/router' 4 | import NProgress from 'nprogress' 5 | import { Menu, Container, Image, Icon } from 'semantic-ui-react' 6 | 7 | Router.onRouteChangeStart = url => NProgress.start() 8 | Router.onRouteChangeComplete = () => NProgress.done() 9 | Router.onRouteChangeError = () => NProgress.done() 10 | 11 | export default ({ token }) => ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | NextJS Store 26 | 27 | 28 | 29 | {token ? ( 30 | 31 | My account 32 | 33 | ) : ( 34 | [ 35 | 36 | Sign up 37 | , 38 | 39 | 40 | Sign in 41 | 42 | ] 43 | )} 44 | 45 | 46 | 47 | 48 | Cart 49 | 50 | 51 | 52 | 53 | 54 | ) 55 | -------------------------------------------------------------------------------- /components/Layout.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import { Container } from 'semantic-ui-react' 3 | 4 | import Header from './Header' 5 | 6 | export default class Layout extends React.Component { 7 | state = { 8 | token: null 9 | } 10 | 11 | componentDidMount() { 12 | const token = localStorage.getItem('customerToken') 13 | 14 | const cartId = localStorage.getItem('mcart') 15 | 16 | if (!cartId) { 17 | const cartId = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'.replace(/[x]/g, () => 18 | ((Math.random() * 16) | 0).toString(16) 19 | ) 20 | localStorage.setItem('mcart', cartId) 21 | } 22 | 23 | this.setState({ 24 | token, 25 | cartId 26 | }) 27 | } 28 | 29 | render() { 30 | const { children, title = '' } = this.props 31 | const { token } = this.state 32 | 33 | return ( 34 | 35 | 36 | 37 | 38 | 42 | {title} 43 | 44 | 45 | 46 | 47 | 48 | {children} 49 | 50 | 51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /components/OrderItemList.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import { Header, Loader, Message, Label, Segment } from 'semantic-ui-react' 3 | 4 | export default ({ orders, loading, included }) => { 5 | if (loading) return 6 | 7 | if (orders.length === 0) { 8 | return ( 9 | 10 | No recent orders 11 | 12 | When you place an order, they will appear here.{' '} 13 | 14 | Go shopping 15 | . 16 | 17 | 18 | ) 19 | } 20 | 21 | return ( 22 | 23 | My previous orders 24 | 25 | {orders.map(order => { 26 | const { 27 | id, 28 | billing_address, 29 | shipping_address, 30 | status, 31 | meta, 32 | timestamps, 33 | order_items 34 | } = order 35 | const completed = status === 'complete' 36 | const price = meta.display_price.with_tax.formatted 37 | 38 | return ( 39 | 40 | 41 | {price} 42 | 47 | {JSON.stringify(order_items, '\t', 2)} 48 | 49 | 50 | 51 | Billing address: 52 | 53 | {billing_address.line_1} 54 | 55 | {billing_address.line_2} 56 | 57 | {billing_address.city} 58 | 59 | {billing_address.postcode} 60 | 61 | {billing_address.county} 62 | 63 | {billing_address.country} 64 | 65 | 66 | 67 | Shipping address: 68 | 69 | {shipping_address.line_1} 70 | 71 | {shipping_address.line_2} 72 | 73 | {shipping_address.city} 74 | 75 | {shipping_address.postcode} 76 | 77 | {shipping_address.county} 78 | 79 | {shipping_address.country} 80 | 81 | 82 | 83 | 84 | ) 85 | })} 86 | 87 | ) 88 | } 89 | -------------------------------------------------------------------------------- /components/ProductAttributes.js: -------------------------------------------------------------------------------- 1 | import { Header, Divider, Table } from 'semantic-ui-react' 2 | 3 | export default ({ 4 | description, 5 | material, 6 | max_watt, 7 | bulb_qty, 8 | finish, 9 | bulb 10 | }) => ( 11 | 12 | About this product 13 | {description} 14 | 15 | 16 | 17 | 18 | 19 | 20 | Attributes 21 | 22 | 23 | 24 | 25 | 26 | Material 27 | {material} 28 | 29 | 30 | Max watt. 31 | {max_watt} 32 | 33 | 34 | Bulb qty. 35 | {bulb_qty} 36 | 37 | 38 | Finish 39 | {finish} 40 | 41 | 42 | Fitting 43 | {bulb} 44 | 45 | 46 | 47 | 48 | ) 49 | -------------------------------------------------------------------------------- /components/ProductList.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import { Card, Image } from 'semantic-ui-react' 3 | 4 | const mapProductsToItems = products => 5 | products.map(({ id, name, image, description, meta }) => { 6 | const price = meta.display_price.with_tax.formatted || null 7 | 8 | return { 9 | childKey: id, 10 | image, 11 | header: name, 12 | meta: price, 13 | fluid: true, 14 | href: `/product?id=${id}` 15 | } 16 | }) 17 | 18 | export default ({ products }) => ( 19 | 20 | ) 21 | -------------------------------------------------------------------------------- /components/ProductSummary.js: -------------------------------------------------------------------------------- 1 | import { Item, Label } from 'semantic-ui-react' 2 | 3 | import AddToCart from './AddToCart' 4 | 5 | export default ({ id, image, name, meta, sku }) => ( 6 | 7 | 8 | 9 | 10 | 11 | {name} 12 | 13 | {meta.display_price.with_tax.formatted} 14 | 15 | SKU: {sku} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | -------------------------------------------------------------------------------- /lib/moltin.js: -------------------------------------------------------------------------------- 1 | import { gateway as MoltinGateway } from '@moltin/sdk' 2 | 3 | const Moltin = MoltinGateway({ 4 | client_id: 5 | process.env.MOLTIN_CLIENT_ID || 'EdP3Gi1agyUF3yFS7Ngm8iyodLgbSR3wY4ceoJl0d2' 6 | }) 7 | 8 | export const getProducts = () => Moltin.Products.With('main_image').All() 9 | 10 | export const getProductById = id => Moltin.Products.With('main_image').Get(id) 11 | 12 | export const addToCart = (cartId, productId, quantity) => 13 | Moltin.Cart(cartId).AddProduct(productId, quantity) 14 | 15 | export const getCartItems = id => Moltin.Cart(id).Items() 16 | 17 | export const removeFromCart = (productId, cartId) => 18 | Moltin.Cart(cartId).RemoveItem(productId) 19 | 20 | export const checkoutCart = (cartId, customer, billing) => 21 | Moltin.Cart(cartId).Checkout(customer, billing) 22 | 23 | export const payForOrder = (orderId, token, email) => 24 | Moltin.Orders.Payment(orderId, { 25 | gateway: 'stripe', 26 | method: 'purchase', 27 | payment: token, 28 | options: { 29 | receipt_email: email 30 | } 31 | }) 32 | 33 | export const register = async ({ email, password, ...rest }) => { 34 | const { 35 | data: { name, id } 36 | } = await Moltin.Customers.Create({ 37 | email, 38 | password, 39 | type: 'customer', 40 | ...rest 41 | }) 42 | 43 | const { token } = await login({ email, password }) 44 | 45 | return { 46 | id, 47 | name, 48 | email, 49 | token 50 | } 51 | } 52 | 53 | export const login = async ({ email, password }) => { 54 | const { 55 | data: { customer_id: id, token } 56 | } = await Moltin.Customers.Token(email, password) 57 | 58 | return { 59 | id, 60 | token 61 | } 62 | } 63 | 64 | export const getOrders = token => Moltin.Orders.With('items').All(token) 65 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | webpack: config => { 3 | config.node = { 4 | fs: 'empty' 5 | } 6 | 7 | return config 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "alias": "moltin-nextjs-demo-store", 3 | "dotenv": true, 4 | "env": { 5 | "NODE_ENV": "production" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "license": "MIT", 4 | "scripts": { 5 | "dev": "next", 6 | "build": "next build", 7 | "start": "next start" 8 | }, 9 | "dependencies": { 10 | "@moltin/sdk": "^3.19.0", 11 | "babel-plugin-inline-dotenv": "^1.1.2", 12 | "babel-plugin-transform-inline-environment-variables": "^0.3.0", 13 | "next": "5.0.0", 14 | "nprogress": "^0.2.0", 15 | "react": "16.2.0", 16 | "react-dom": "16.2.0", 17 | "react-stripe-checkout": "^2.6.3", 18 | "semantic-ui-react": "0.78.3" 19 | }, 20 | "devDependencies": { 21 | "babel-plugin-inline-dotenv": "^1.1.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pages/cart.js: -------------------------------------------------------------------------------- 1 | import Layout from '../components/Layout' 2 | import CartItemList from '../components/CartItemList' 3 | import CartSummary from '../components/CartSummary' 4 | 5 | import { 6 | getCartItems, 7 | removeFromCart, 8 | checkoutCart, 9 | payForOrder 10 | } from '../lib/moltin' 11 | 12 | export default class Cart extends React.Component { 13 | state = { 14 | items: [], 15 | loading: true, 16 | completed: false 17 | } 18 | 19 | async componentDidMount() { 20 | const cartId = await localStorage.getItem('mcart') 21 | const { data, meta } = await getCartItems(cartId) 22 | 23 | this.setState({ 24 | items: data, 25 | meta, 26 | cartId, 27 | loading: false 28 | }) 29 | } 30 | 31 | _handleCheckout = async data => { 32 | const cartId = await localStorage.getItem('mcart') 33 | const customerId = localStorage.getItem('mcustomer') 34 | 35 | const { 36 | id: token, 37 | email, 38 | card: { 39 | name, 40 | address_line1: line_1, 41 | address_city: city, 42 | address_country: country, 43 | address_state: county, 44 | address_zip: postcode 45 | } 46 | } = data 47 | 48 | const customer = customerId ? customerId : { name, email } 49 | 50 | const address = { 51 | first_name: name.split(' ')[0], 52 | last_name: name.split(' ')[1], 53 | line_1, 54 | city, 55 | county, 56 | country, 57 | postcode 58 | } 59 | 60 | try { 61 | const { 62 | data: { id } 63 | } = await checkoutCart(cartId, customer, address) 64 | await payForOrder(id, token, email) 65 | 66 | this.setState({ 67 | completed: true 68 | }) 69 | } catch (e) { 70 | console.log(e) 71 | } 72 | } 73 | 74 | _handleRemoveFromCart = async itemId => { 75 | const { items, cartId } = this.state 76 | const { data, meta } = await removeFromCart(itemId, cartId) 77 | 78 | this.setState({ 79 | items: data, 80 | meta 81 | }) 82 | } 83 | 84 | render() { 85 | const { meta, ...rest } = this.state 86 | const { loading } = rest 87 | 88 | return ( 89 | 90 | 91 | {!loading && !rest.completed && ( 92 | 93 | )} 94 | 95 | ) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Layout from '../components/Layout' 2 | import ProductList from '../components/ProductList' 3 | 4 | import { getProducts } from '../lib/moltin' 5 | 6 | const Home = ({ products }) => ( 7 | 8 | 9 | 10 | ) 11 | 12 | Home.getInitialProps = async () => { 13 | const { data, included } = await getProducts() 14 | 15 | const products = data.map(product => { 16 | const imageId = product.relationships.main_image 17 | ? product.relationships.main_image.data.id 18 | : false 19 | 20 | return { 21 | ...product, 22 | image: imageId 23 | ? included.main_images.find(img => img.id === imageId).link.href 24 | : '/static/moltin-light-hex.svg' 25 | } 26 | }) 27 | 28 | return { 29 | products 30 | } 31 | } 32 | 33 | export default Home 34 | -------------------------------------------------------------------------------- /pages/login.js: -------------------------------------------------------------------------------- 1 | import Router from 'next/router' 2 | import { 3 | Header, 4 | Form, 5 | Input, 6 | Button, 7 | Segment, 8 | Message 9 | } from 'semantic-ui-react' 10 | 11 | import Layout from '../components/Layout' 12 | 13 | import { login } from '../lib/moltin' 14 | 15 | export default class Login extends React.Component { 16 | state = { 17 | email: '', 18 | password: '', 19 | loading: false, 20 | errors: null 21 | } 22 | 23 | _handleSubmit = async e => { 24 | e.preventDefault() 25 | 26 | const { email, password } = this.state 27 | 28 | this.setState({ 29 | loading: true, 30 | errors: null 31 | }) 32 | 33 | try { 34 | const { id, token } = await login({ email, password }) 35 | localStorage.setItem('customerToken', token) 36 | localStorage.setItem('mcustomer', id) 37 | Router.push('/myaccount') 38 | } catch (e) { 39 | console.log(e.message) 40 | this.setState({ 41 | loading: false, 42 | errors: e 43 | }) 44 | } 45 | } 46 | 47 | _handleChange = ({ target: { name, value } }) => 48 | this.setState({ [name]: value }) 49 | 50 | render() { 51 | const { loading, errors } = this.state 52 | 53 | return ( 54 | 55 | Log in to your account 56 | 57 | 58 | 63 | 64 | 65 | 66 | Email 67 | this._handleChange(e)} 72 | /> 73 | 74 | 75 | 76 | Password 77 | this._handleChange(e)} 82 | /> 83 | 84 | 85 | 86 | Login 87 | 88 | 89 | 90 | 91 | ) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /pages/myaccount.js: -------------------------------------------------------------------------------- 1 | import Router from 'next/router' 2 | 3 | import Layout from '../components/Layout' 4 | import OrderItemList from '../components/OrderItemList' 5 | 6 | import { getOrders } from '../lib/moltin' 7 | 8 | export default class MyAccount extends React.Component { 9 | state = { 10 | loading: true, 11 | orders: [] 12 | } 13 | 14 | async componentDidMount() { 15 | const token = localStorage.getItem('customerToken') 16 | 17 | if (!token) { 18 | Router.push('/login') 19 | } 20 | 21 | const { data, included, meta } = await getOrders(token) 22 | 23 | const orders = data.map(order => { 24 | // const orderItems = order.relationships.items.data 25 | // const includedItems = included.items.map(i => i.id === ) 26 | 27 | return { 28 | ...order 29 | } 30 | }) 31 | console.log(orders) 32 | 33 | this.setState({ 34 | loading: false, 35 | orders, 36 | included, 37 | meta 38 | }) 39 | 40 | console.log(data) 41 | console.log(included) 42 | } 43 | 44 | render() { 45 | return ( 46 | 47 | 48 | 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pages/product.js: -------------------------------------------------------------------------------- 1 | import Layout from '../components/Layout' 2 | import ProductSummary from '../components/ProductSummary' 3 | import ProductAttributes from '../components/ProductAttributes' 4 | 5 | import { getProductById } from '../lib/moltin' 6 | 7 | const ProductPage = ({ product }) => ( 8 | 9 | 10 | 11 | 12 | ) 13 | 14 | ProductPage.getInitialProps = async ({ query: { id } }) => { 15 | const { 16 | data, 17 | included: { main_images } 18 | } = await getProductById(id) 19 | 20 | const imageId = data.relationships.main_image 21 | ? data.relationships.main_image.data.id 22 | : false 23 | 24 | return { 25 | product: { 26 | ...data, 27 | image: imageId 28 | ? main_images.find(img => img.id === imageId).link.href 29 | : '/static/moltin-light-hex.svg' 30 | } 31 | } 32 | } 33 | 34 | export default ProductPage 35 | -------------------------------------------------------------------------------- /pages/register.js: -------------------------------------------------------------------------------- 1 | import Router from 'next/router' 2 | import { Header, Form, Input, Button, Segment } from 'semantic-ui-react' 3 | 4 | import Layout from '../components/Layout' 5 | 6 | import { register } from '../lib/moltin' 7 | 8 | export default class Register extends React.Component { 9 | state = { 10 | name: '', 11 | email: '', 12 | password: '', 13 | loading: false 14 | } 15 | 16 | _handleSubmit = async e => { 17 | e.preventDefault() 18 | 19 | const { name, email, password } = this.state 20 | 21 | this.setState({ 22 | loading: true 23 | }) 24 | 25 | try { 26 | const { id, token } = await register({ name, email, password }) 27 | localStorage.setItem('customerToken', token) 28 | localStorage.setItem('mcustomer', id) 29 | Router.push('/myaccount') 30 | } catch (e) { 31 | this.setState({ 32 | loading: false 33 | }) 34 | } 35 | } 36 | 37 | _handleChange = ({ target: { name, value } }) => 38 | this.setState({ [name]: value }) 39 | 40 | render() { 41 | const { loading } = this.state 42 | 43 | return ( 44 | 45 | Create an account 46 | 47 | 48 | 49 | 50 | Name 51 | this._handleChange(e)} 56 | /> 57 | 58 | 59 | 60 | Email 61 | this._handleChange(e)} 66 | /> 67 | 68 | 69 | 70 | Password 71 | this._handleChange(e)} 76 | /> 77 | 78 | 79 | 80 | Register 81 | 82 | 83 | 84 | 85 | ) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /static/moltin-light-hex.svg: -------------------------------------------------------------------------------- 1 | light-hex -------------------------------------------------------------------------------- /static/nprogress.css: -------------------------------------------------------------------------------- 1 | /* Make clicks pass-through */ 2 | #nprogress { 3 | pointer-events: none; 4 | } 5 | 6 | #nprogress .bar { 7 | background: #e67417; 8 | 9 | position: fixed; 10 | z-index: 1031; 11 | top: 0; 12 | left: 0; 13 | 14 | width: 100%; 15 | height: 2px; 16 | } 17 | 18 | /* Fancy blur effect */ 19 | #nprogress .peg { 20 | display: block; 21 | position: absolute; 22 | right: 0px; 23 | width: 100px; 24 | height: 100%; 25 | box-shadow: 0 0 10px #e67417, 0 0 5px #e67417; 26 | opacity: 1; 27 | 28 | -webkit-transform: rotate(3deg) translate(0px, -4px); 29 | -ms-transform: rotate(3deg) translate(0px, -4px); 30 | transform: rotate(3deg) translate(0px, -4px); 31 | } 32 | 33 | /* Remove these to get rid of the spinner */ 34 | #nprogress .spinner { 35 | display: block; 36 | position: fixed; 37 | z-index: 1031; 38 | top: 15px; 39 | right: 15px; 40 | } 41 | 42 | #nprogress .spinner-icon { 43 | width: 18px; 44 | height: 18px; 45 | box-sizing: border-box; 46 | 47 | border: solid 2px transparent; 48 | border-top-color: #e67417; 49 | border-left-color: #e67417; 50 | border-radius: 50%; 51 | 52 | -webkit-animation: nprogress-spinner 400ms linear infinite; 53 | animation: nprogress-spinner 400ms linear infinite; 54 | } 55 | 56 | .nprogress-custom-parent { 57 | overflow: hidden; 58 | position: relative; 59 | } 60 | 61 | .nprogress-custom-parent #nprogress .spinner, 62 | .nprogress-custom-parent #nprogress .bar { 63 | position: absolute; 64 | } 65 | 66 | @-webkit-keyframes nprogress-spinner { 67 | 0% { 68 | -webkit-transform: rotate(0deg); 69 | } 70 | 100% { 71 | -webkit-transform: rotate(360deg); 72 | } 73 | } 74 | @keyframes nprogress-spinner { 75 | 0% { 76 | transform: rotate(0deg); 77 | } 78 | 100% { 79 | transform: rotate(360deg); 80 | } 81 | } 82 | --------------------------------------------------------------------------------
Congratulations. Your order and payment has been accepted.
20 | You'll need to add some items to the cart before you can checkout. 21 |
12 | When you place an order, they will appear here.{' '} 13 | 14 | Go shopping 15 | . 16 |
{JSON.stringify(order_items, '\t', 2)}
53 | {billing_address.line_1} 54 | 55 | {billing_address.line_2} 56 | 57 | {billing_address.city} 58 | 59 | {billing_address.postcode} 60 | 61 | {billing_address.county} 62 | 63 | {billing_address.country} 64 |
69 | {shipping_address.line_1} 70 | 71 | {shipping_address.line_2} 72 | 73 | {shipping_address.city} 74 | 75 | {shipping_address.postcode} 76 | 77 | {shipping_address.county} 78 | 79 | {shipping_address.country} 80 |
{description}
{meta.display_price.with_tax.formatted}