├── .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 | [](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 |
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 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------