├── .babelrc.js ├── .gitignore ├── README.md ├── components ├── Contact.tsx ├── Footer.tsx ├── Header.tsx ├── Product.tsx └── ProductList.tsx ├── next.config.js ├── package-lock.json ├── package.json ├── pages └── index.tsx ├── static ├── aquarium.svg ├── crowntail.jpg ├── dragonscale.jpg ├── favicon.ico ├── halfmoon.jpg ├── logo.svg └── veiltail.jpg └── styles.scss /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['next/babel', '@zeit/next-typescript/babel'] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js E-Commerce Tutorial: Quick Shopping Cart Integration 2 | 3 | ![next-js-ecommerce](https://snipcart.com/media/204366/next-js-ecommerce.png) 4 | 5 | The creation of tools such as Next.js that successfully simplified React frontend development. 6 | 7 | Here, I want to explore what Next.js can do for e-commerce. 8 | 9 | Steps: 10 | 11 | - Set up a Next.js development environment 12 | - Create new pages & components 13 | - Fetch data & import components 14 | - Add a shopping cart to a Next.js app 15 | - Style & deploy the app 16 | 17 | > [Read the full tutorial](https://snipcart.com/blog/next-js-ecommerce-tutorial) 18 | 19 | > [See the live demo](https://snipcart-nextjs.herokuapp.com/) 20 | 21 | Enjoy folks! 22 | -------------------------------------------------------------------------------- /components/Contact.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | export default function Contact() { 4 | return ( 5 |
6 |

Any questions? Contact us.

7 |

We're looking forward to hearing from you. Feel free to contact us if you have any questions!

8 | 9 | 10 | 11 |
12 | ) 13 | } -------------------------------------------------------------------------------- /components/Footer.tsx: -------------------------------------------------------------------------------- 1 | export default function Footer(){ 2 | return ( 3 | 11 | ) 12 | } -------------------------------------------------------------------------------- /components/Header.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | export default function Header() { 4 | return ( 5 |
6 | 7 | 8 | 9 | 10 |

FishCastle

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | ) 20 | } -------------------------------------------------------------------------------- /components/Product.tsx: -------------------------------------------------------------------------------- 1 | import {withRouter, RouterProps} from 'next/router' 2 | 3 | export interface IProduct { 4 | id: string 5 | name: string 6 | price: number 7 | url: string 8 | description: string 9 | image: string 10 | } 11 | 12 | interface IProductProps { 13 | product: IProduct 14 | router: RouterProps 15 | } 16 | 17 | const Product = (props: IProductProps) => { 18 | return ( 19 |
20 |

{props.product.name}

21 |

{props.product.description}

22 | 23 |
24 |
${props.product.price.toFixed(2)}
25 | 34 |
35 |
36 | ) 37 | } 38 | 39 | export default withRouter(Product) -------------------------------------------------------------------------------- /components/ProductList.tsx: -------------------------------------------------------------------------------- 1 | import Product, { IProduct } from "./Product" 2 | 3 | interface IProductListProps { 4 | products: IProduct[] 5 | } 6 | 7 | const ProductList = (props: IProductListProps) => { 8 | return ( 9 |
10 | {props.products.map((product, index) => )} 11 |
12 | ) 13 | } 14 | 15 | export default ProductList -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withTypescript = require('@zeit/next-typescript') 2 | const withSass = require('@zeit/next-sass') 3 | 4 | module.exports = withTypescript(withSass()); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snipcart-nextjs", 3 | "version": "1.0.0", 4 | "description": "snipcart-nextjs", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "next", 8 | "build": "next build", 9 | "start": "next start" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/snipcart/snipcart-nextjs.git" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/snipcart/snipcart-nextjs/issues" 20 | }, 21 | "homepage": "https://github.com/snipcart/snipcart-nextjs#readme", 22 | "dependencies": { 23 | "@types/next": "^8.0.5", 24 | "@types/react": "^16.8.17", 25 | "@types/react-dom": "^16.8.4", 26 | "@zeit/next-sass": "^1.0.1", 27 | "@zeit/next-typescript": "^1.1.1", 28 | "next": "^8.1.0", 29 | "node-sass": "^4.12.0", 30 | "react": "^16.8.6", 31 | "react-dom": "^16.8.6" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Header from "../components/Header" 2 | import ProductList from "../components/ProductList" 3 | import { IProduct } from "../components/Product" 4 | import Footer from "../components/Footer" 5 | import Contact from "../components/Contact" 6 | import Head from "next/head" 7 | 8 | import "../styles.scss" 9 | 10 | interface IIndexProps { 11 | products: IProduct[] 12 | } 13 | 14 | const Index = (props: IIndexProps) => { 15 | return ( 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | a 26 |
27 |

REDISCOVER

28 |

Fishkeeping

29 |

An exclusive collection of bettas available for everyone.

30 |
31 | 32 | 33 |
34 |
36 | ) 37 | } 38 | 39 | Index.getInitialProps = async () => { 40 | return { 41 | products: [ 42 | {id: "nextjs_halfmoon", name: "Halfmoon Betta", price: 25.00, image: "../static/halfmoon.jpg", description: "The Halfmoon betta is arguably one of the prettiest betta species. It is recognized by its large tail that can flare up to 180 degrees."} as IProduct, 43 | {id: "nextjs_dragonscale", name: "Dragon Scale Betta", price: 35, image: "../static/dragonscale.jpg",description: "The dragon scale betta is a rarer and higher maintenance fish. It is named by its thick white scales covering his sides that looks like dragon scale armor."} as IProduct, 44 | {id: "nextjs_crowntail", name: "Crowntail Betta", price: 7.50, image: "../static/crowntail.jpg", description: "The crowntail is pretty common, but interesting none the less. It's recognized by the shape of its tail that has an appearance of a comb."} as IProduct, 45 | {id: "nextjs_veiltail", name: "Veiltail Betta", price: 5.00, image: "../static/veiltail.jpg", description: "By far the most common betta fish. You can recognize it by its long tail aiming downwards."} as IProduct, 46 | ] 47 | } 48 | } 49 | 50 | export default Index -------------------------------------------------------------------------------- /static/aquarium.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/crowntail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snipcart/snipcart-nextjs/0a8c9e7268fbf047103cfbc3de4d395bbf2c22e1/static/crowntail.jpg -------------------------------------------------------------------------------- /static/dragonscale.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snipcart/snipcart-nextjs/0a8c9e7268fbf047103cfbc3de4d395bbf2c22e1/static/dragonscale.jpg -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snipcart/snipcart-nextjs/0a8c9e7268fbf047103cfbc3de4d395bbf2c22e1/static/favicon.ico -------------------------------------------------------------------------------- /static/halfmoon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snipcart/snipcart-nextjs/0a8c9e7268fbf047103cfbc3de4d395bbf2c22e1/static/halfmoon.jpg -------------------------------------------------------------------------------- /static/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /static/veiltail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snipcart/snipcart-nextjs/0a8c9e7268fbf047103cfbc3de4d395bbf2c22e1/static/veiltail.jpg -------------------------------------------------------------------------------- /styles.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Open+Sans:400,600,700,800'); 2 | 3 | $color-purple: #9094FF; 4 | $color-light-grey: #EBEBEB; 5 | $color-dark: #343434; 6 | $max-width: 1000px; 7 | 8 | @mixin centered { 9 | width: 100%; 10 | max-width: $max-width; 11 | margin-left: auto; 12 | margin-right: auto; 13 | 14 | @include smallerThanDesktop { 15 | padding-left: 20px; 16 | padding-right: 20px; 17 | } 18 | } 19 | 20 | @mixin mobile { 21 | @media (max-width: '600px') { 22 | @content; 23 | } 24 | } 25 | 26 | @mixin smallerThanDesktop { 27 | @media (max-width: '1000px') { 28 | @content; 29 | } 30 | } 31 | 32 | * { 33 | box-sizing: border-box; 34 | font-family: 'Open Sans', sans-serif; 35 | font-size: 16px; 36 | } 37 | 38 | html, body { 39 | margin: 0; 40 | padding: 0; 41 | } 42 | 43 | .app { 44 | min-height: 100vh; 45 | display: grid; 46 | grid-template-rows: auto 1fr auto; 47 | } 48 | 49 | .header { 50 | @include centered; 51 | 52 | display: flex; 53 | padding-top: 20px; 54 | padding-bottom: 20px; 55 | 56 | &__summary { 57 | font-weight: bold; 58 | display: flex; 59 | justify-content: center; 60 | align-items: center; 61 | margin-left: auto; 62 | } 63 | 64 | &__price { 65 | color: $color-purple; 66 | margin-left: 10px; 67 | } 68 | 69 | &__logo { 70 | height: 38px; 71 | } 72 | 73 | &__title { 74 | margin: 0; 75 | padding: 0; 76 | font-size: 24px; 77 | margin-left: 20px; 78 | } 79 | } 80 | 81 | .main { 82 | 83 | } 84 | 85 | .background-image { 86 | position: absolute; 87 | opacity: 0.02; 88 | left: -10vw; 89 | top: -10vh; 90 | height: 75vh; 91 | transform: rotate(20deg); 92 | pointer-events: none; 93 | } 94 | 95 | .promotional-message { 96 | margin-bottom: 100px; 97 | margin-top: 25px; 98 | text-align: center; 99 | 100 | h3 { 101 | font-size: 20px; 102 | line-height: normal; 103 | text-align: center; 104 | letter-spacing: 0.4em; 105 | text-transform: uppercase; 106 | margin: 0; 107 | } 108 | 109 | h2 { 110 | font-size: 100px; 111 | @include mobile { font-size: 50px; } 112 | margin: 0; 113 | color: $color-purple; 114 | } 115 | } 116 | 117 | .product-list { 118 | @include centered; 119 | } 120 | 121 | .product { 122 | display: grid; 123 | width: 100%; 124 | 125 | display: grid; 126 | grid-template-areas: 127 | "title title image" 128 | "description description image" 129 | "button button image" 130 | ". . image"; 131 | grid-template-columns: 1fr 1fr 3fr; 132 | margin-bottom: 100px; 133 | grid-column-gap: 100px; 134 | 135 | &:nth-of-type(odd) { 136 | grid-template-areas: 137 | "image title title" 138 | "image description description" 139 | "image button button" 140 | "image . ."; 141 | grid-template-columns: 3fr 1fr 1fr; 142 | 143 | @include mobile { 144 | grid-template-areas: 145 | "image image " 146 | "title title " 147 | "description description" 148 | "button button "; 149 | grid-template-columns: 1fr 1fr; 150 | 151 | img { 152 | height: 300px; 153 | width: 100%; 154 | margin-bottom: 30px; 155 | } 156 | } 157 | } 158 | 159 | &__title { 160 | margin: 0; 161 | grid-area: title; 162 | font-size: 32px; 163 | font-weight: bold; 164 | } 165 | 166 | &__description { 167 | grid-area: description; 168 | line-height: 1.75rem; 169 | min-height: 175px; 170 | @include mobile { 171 | min-height: 0px; 172 | } 173 | } 174 | 175 | &__price { 176 | grid-area: price; 177 | font-size: 28px; 178 | font-weight: bold; 179 | } 180 | 181 | &__image { 182 | grid-area: image; 183 | width: 100%; 184 | height: 100%; 185 | object-fit: cover; 186 | border-radius: 4px; 187 | box-shadow: 0px 18.025px 43.775px rgba(0, 0, 0, 0.25); 188 | } 189 | 190 | &__price-button-container { 191 | display: flex; 192 | grid-area: button; 193 | } 194 | 195 | &__button { 196 | margin-left: 30px; 197 | font-size: 14px; 198 | font-weight: bold; 199 | border-radius: 4px; 200 | padding: 6px; 201 | padding-left: 20px; 202 | padding-right: 20px; 203 | border: none; 204 | background-color: $color-purple; 205 | color: white; 206 | position: relative; 207 | 208 | &:hover { 209 | transition: 0.2s all; 210 | &:before { 211 | transform: scale(1.15, 1.4); 212 | } 213 | } 214 | 215 | &:before { 216 | content: ' '; 217 | position: absolute; 218 | background-color: $color-purple; 219 | top: 0; 220 | left: 0; 221 | border-radius: 4px; 222 | width: 100%; 223 | height: 100%; 224 | opacity: 0.4; 225 | z-index: -1; 226 | transform: scale(1); 227 | transition: all 0.3s cubic-bezier(0.16, 0.8, 0.66, 1.54); 228 | } 229 | } 230 | 231 | @include mobile { 232 | grid-template-areas: 233 | "image image " 234 | "title title " 235 | "description description" 236 | "button button "; 237 | grid-template-columns: 1fr 1fr; 238 | 239 | img { 240 | height: 300px; 241 | width: 100%; 242 | margin-bottom: 30px; 243 | } 244 | } 245 | } 246 | 247 | .contact { 248 | text-align: center; 249 | background-color: $color-light-grey; 250 | padding-top: 100px; 251 | padding-bottom: 100px; 252 | &__title { 253 | font-size: 36px; 254 | 255 | .colored { 256 | font-size: inherit; 257 | color: $color-purple; 258 | } 259 | } 260 | 261 | &__paragraph { 262 | @include centered; 263 | font-weight: 600; 264 | } 265 | 266 | button { 267 | margin-top: 15px; 268 | font-size: 14px; 269 | font-weight: bold; 270 | border-radius: 4px; 271 | padding: 12px; 272 | padding-left: 20px; 273 | padding-right: 20px; 274 | border: none; 275 | background-color: $color-dark; 276 | color: white; 277 | position: relative; 278 | z-index: 99; 279 | 280 | &:hover { 281 | transition: 0.2s all; 282 | &:before { 283 | transform: scale(1.15, 1.4); 284 | } 285 | } 286 | 287 | &:before { 288 | content: ' '; 289 | position: absolute; 290 | background-color: $color-dark; 291 | top: 0; 292 | left: 0; 293 | border-radius: 4px; 294 | width: 100%; 295 | height: 100%; 296 | opacity: 0.4; 297 | z-index: -1; 298 | transform: scale(1); 299 | transition: all 0.3s cubic-bezier(0.16, 0.8, 0.66, 1.54); 300 | } 301 | } 302 | } 303 | 304 | .footer { 305 | color: white; 306 | background: linear-gradient(90deg, #707070 0%, #474747 100%); 307 | &__left { 308 | margin-left: auto; 309 | } 310 | 311 | a { 312 | color: white; 313 | } 314 | p { 315 | display: flex; 316 | margin: 0; 317 | @include centered; 318 | padding-top: 20px; 319 | padding-bottom: 20px; 320 | } 321 | } --------------------------------------------------------------------------------