├── .example.env ├── .gitignore ├── README.md ├── components ├── Header.js ├── ProductCard.js └── layout.js ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── _document.js ├── api │ └── revalidate.js ├── index.js └── products │ └── [slug].js ├── postcss.config.js ├── prettier.config.js ├── public ├── favicon.ico ├── hero.jpg └── vercel.svg ├── styles └── globals.css ├── swell.js └── tailwind.config.js /.example.env: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_SWELL_STORE_ID= 2 | NEXT_PUBLIC_SWELL_PUBLIC_KEY= 3 | REVALIDATE_SECRET= 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | .env 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swell / Next.js App 2 | 3 | Learn how to create a headless ecommerce application using Swell and Next.js 4 | 5 | ## Built With 6 | 7 | - [Swell](https://swell.is/) 8 | - [Tailwind CSS](https://tailwindcss.com) 9 | - [Next.js](https://nextjs.org) 10 | - [Vercel](https://vercel.com) 11 | 12 | ## Deploy with Vercel 13 | 14 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnutlope%2Fnextjs-swell&env=NEXT_PUBLIC_SWELL_STORE_ID,NEXT_PUBLIC_SWELL_PUBLIC_KEY,REVALIDATE_SECRET&envDescription=API%20Keys%20from%20Swell%20needed%20to%20run%20this%20application.) 15 | -------------------------------------------------------------------------------- /components/Header.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | 3 | export default function Header({ scrollHandler }) { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 | People working on laptops 17 |
18 |
19 |
20 |

21 | The Coffee House 22 |

23 |

24 | Life is better with 25 | coffee 26 |

27 | 28 |
29 | 35 |
36 |
37 |
38 |
39 |
40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /components/ProductCard.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import Link from 'next/link' 3 | import { useState } from 'react' 4 | 5 | export default function ProductCard({ product }) { 6 | const [isLoading, setLoading] = useState(true) 7 | function cn(...classes) { 8 | return classes.filter(Boolean).join(' ') 9 | } 10 | return ( 11 | 12 |
13 | setLoading(false)} 25 | /> 26 |
27 |
28 |

{product.name}

29 |

${product.price}

30 |
31 |

32 | {product.options[0].values[0].name} calories 33 |

34 | 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /components/layout.js: -------------------------------------------------------------------------------- 1 | export default function Layout({ children }) { 2 | return ( 3 | <> 4 |
{children}
5 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | images: { 4 | domains: ['cdn.schema.io'], 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swell-nextjs-app", 3 | "private": true, 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start" 8 | }, 9 | "dependencies": { 10 | "swell-js": "^3.13.1" 11 | }, 12 | "devDependencies": { 13 | "@tailwindcss/aspect-ratio": "^0.4.0", 14 | "@types/node": "17.0.4", 15 | "@types/react": "17.0.38", 16 | "autoprefixer": "^10.4.0", 17 | "eslint": "8.13.0", 18 | "eslint-config-next": "^13.1.3", 19 | "next": "^13.1.3", 20 | "postcss": "^8.4.5", 21 | "prettier": "^2.5.1", 22 | "prettier-plugin-tailwindcss": "^0.1.1", 23 | "react": "^18.2.0", 24 | "react-dom": "^18.2.0", 25 | "tailwindcss": "^3.0.7", 26 | "typescript": "4.5.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import Layout from '../components/layout' 3 | import Head from 'next/head' 4 | 5 | function MyApp({ Component, pageProps }) { 6 | return ( 7 | <> 8 | 9 | The Coffee House 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | } 17 | 18 | export default MyApp 19 | -------------------------------------------------------------------------------- /pages/_document.js: -------------------------------------------------------------------------------- 1 | import Document, { Html, Head, Main, NextScript } from 'next/document' 2 | 3 | class MyDocument extends Document { 4 | render() { 5 | return ( 6 | 7 | 8 | 12 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | ) 23 | } 24 | } 25 | 26 | export default MyDocument 27 | -------------------------------------------------------------------------------- /pages/api/revalidate.js: -------------------------------------------------------------------------------- 1 | export default async function handler(req, res) { 2 | if (req.query.secret !== process.env.REVALIDATE_SECRET) { 3 | return res.status(401).json({ message: 'Invalid token' }) 4 | } 5 | 6 | // Regenerate our index and product routes 7 | try { 8 | await res.revalidate('/') 9 | await res.revalidate(`/products/${req.body.data.id}`) 10 | return res.status(200).json({ revalidated: true }) 11 | } catch (err) { 12 | return res.status(500).send('Error revalidating') 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import ProductCard from '../components/ProductCard' 2 | import swell from '../swell' 3 | import Header from '../components/Header' 4 | import { useRef } from 'react' 5 | 6 | export default function Gallery({ data }) { 7 | let coffeeRef = useRef() 8 | 9 | const scrollHandler = (e) => { 10 | e.preventDefault() 11 | coffeeRef.scrollIntoView({ 12 | behavior: 'smooth', 13 | block: 'start', 14 | }) 15 | } 16 | 17 | return ( 18 | <> 19 |
20 |
21 |
22 |
23 |

(coffeeRef = element)} 26 | > 27 | Crafted by us, for you 28 |

29 |
30 |
31 |
32 | {data && 33 | data.results.map((product) => ( 34 | 35 | ))} 36 |
37 |
38 | 39 | ) 40 | } 41 | 42 | export async function getStaticProps() { 43 | const swellProducts = await swell.products.list() 44 | 45 | return { 46 | props: { 47 | data: swellProducts, 48 | }, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pages/products/[slug].js: -------------------------------------------------------------------------------- 1 | import swell from '../../swell' 2 | import Image from 'next/image' 3 | import { useRouter } from 'next/router' 4 | 5 | export default function Product({ product }) { 6 | const router = useRouter() 7 | async function checkout(productId) { 8 | await swell.cart.setItems([]) 9 | await swell.cart.addItem({ 10 | product_id: productId, 11 | quantity: 1, 12 | }) 13 | const cart = await swell.cart.get() 14 | router.push(cart.checkout_url) 15 | } 16 | return ( 17 |
18 |
19 |
20 | coffee 28 |
29 |

30 | {product.name} 31 |

32 |

33 | ${product.price} 34 |

35 | 41 |
42 | Description 43 |
44 |

{product.description}

45 |
46 |
47 |
48 |
49 | ) 50 | } 51 | 52 | export async function getStaticProps({ params }) { 53 | const swellProduct = await swell.products.get(params.slug) 54 | return { 55 | props: { 56 | product: swellProduct, 57 | }, 58 | } 59 | } 60 | 61 | export async function getStaticPaths() { 62 | const swellProducts = await swell.products.list() 63 | let fullPaths = [] 64 | for (let product of swellProducts.results) { 65 | fullPaths.push({ params: { slug: product.id } }) 66 | } 67 | 68 | return { 69 | paths: fullPaths, 70 | fallback: 'blocking', 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | semi: false, 4 | } 5 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nutlope/nextjs-swell/8c6df2c4ca86d955d689b0f2d77ef9d95330ebb1/public/favicon.ico -------------------------------------------------------------------------------- /public/hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nutlope/nextjs-swell/8c6df2c4ca86d955d689b0f2d77ef9d95330ebb1/public/hero.jpg -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | * { 6 | font-family: Montserrat, sans-serif; 7 | } 8 | -------------------------------------------------------------------------------- /swell.js: -------------------------------------------------------------------------------- 1 | import swell from 'swell-js' 2 | 3 | const SWELL_STORE_ID = process.env.NEXT_PUBLIC_SWELL_STORE_ID 4 | const SWELL_PUBLIC_KEY = process.env.NEXT_PUBLIC_SWELL_PUBLIC_KEY 5 | 6 | swell.init(SWELL_STORE_ID, SWELL_PUBLIC_KEY) 7 | 8 | export default swell 9 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | './pages/**/*.{js,ts,jsx,tsx}', 4 | './components/**/*.{js,ts,jsx,tsx}', 5 | ], 6 | plugins: [require('@tailwindcss/aspect-ratio')], 7 | } 8 | --------------------------------------------------------------------------------