├── public
├── hero.jpg
├── hero.png
├── adidas.jpg
├── favicon.ico
├── favicon.png
├── hero.webp
└── hero.svg
├── styles
├── test.scss
└── theme.js
├── pages
├── shop.js
├── success.js
├── _document.js
├── index.js
├── _app.js
├── cart.js
├── checkout.js
└── product
│ └── [permalink].js
├── next.config.js
├── README.md
├── lib
└── commerce.js
├── jsconfig.json
├── utils
└── fetcher.js
├── components
├── DarkModeSwitch.js
├── Footer.js
├── Shipping
│ ├── Success.js
│ ├── OrderTotal.js
│ ├── Payment.js
│ └── Shipping.js
├── RelatedProducts.js
├── VariantPicker.js
├── SearchBar.js
├── Hero.js
├── NavBar.js
└── Product
│ ├── Products.js
│ ├── Product.js
│ └── ProductButton.js
├── .gitignore
├── package.json
├── LICENSE
└── context
├── cart.js
└── checkout.js
/public/hero.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarsimranBarki/rapid-store/HEAD/public/hero.jpg
--------------------------------------------------------------------------------
/public/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarsimranBarki/rapid-store/HEAD/public/hero.png
--------------------------------------------------------------------------------
/styles/test.scss:
--------------------------------------------------------------------------------
1 | .harsimran{
2 | background-color: 'red';
3 | color: 'green';
4 | }
--------------------------------------------------------------------------------
/public/adidas.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarsimranBarki/rapid-store/HEAD/public/adidas.jpg
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarsimranBarki/rapid-store/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarsimranBarki/rapid-store/HEAD/public/favicon.png
--------------------------------------------------------------------------------
/public/hero.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarsimranBarki/rapid-store/HEAD/public/hero.webp
--------------------------------------------------------------------------------
/pages/shop.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function shop() {
4 | return
;
5 | }
6 |
7 | export default shop;
8 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withImages = require("next-images");
2 | module.exports = withImages({
3 | images: {
4 | domains: ["cdn.chec.io"],
5 | },
6 | });
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rapid Store - Ecommerce Website
2 |
3 | ## Tech Stack
4 |
5 | - Chakra UI
6 | - Next JS
7 | - Ecommerce
8 | - Stripe
9 |
10 | ### Deployed using vercel
11 |
--------------------------------------------------------------------------------
/lib/commerce.js:
--------------------------------------------------------------------------------
1 | import CommerceSDK from "@chec/commerce.js";
2 |
3 | const client = new CommerceSDK(process.env.NEXT_PUBLIC_CHEC_PUBLIC_API_KEY);
4 |
5 | export default client;
6 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/components/*": ["components/*"],
6 | "@/lib/*": ["lib/*"],
7 | "@/styles/*": ["styles/*"],
8 | "@/utils/*": ["utils/*"],
9 | "@/context/*": ["context/*"]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/utils/fetcher.js:
--------------------------------------------------------------------------------
1 | const fetcher = async (url, token) => {
2 | const res = await fetch(url, {
3 | method: "GET",
4 | headers: new Headers({ "Content-Type": "application/json", token }),
5 | credentials: "same-origin",
6 | });
7 |
8 | return res.json();
9 | };
10 |
11 | export default fetcher;
12 |
--------------------------------------------------------------------------------
/pages/success.js:
--------------------------------------------------------------------------------
1 | import Success from "@/components/Shipping/Success";
2 | import { Grid } from "@chakra-ui/layout";
3 | import React from "react";
4 |
5 | function success() {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default success;
14 |
--------------------------------------------------------------------------------
/components/DarkModeSwitch.js:
--------------------------------------------------------------------------------
1 | import { useColorMode, Switch } from '@chakra-ui/react'
2 |
3 | export const DarkModeSwitch = () => {
4 | const { colorMode, toggleColorMode } = useColorMode()
5 | const isDark = colorMode === 'dark'
6 | return (
7 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/.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 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/components/Footer.js:
--------------------------------------------------------------------------------
1 | import { Box, HStack, Text } from '@chakra-ui/layout'
2 | import React from 'react'
3 |
4 | function Footer() {
5 | return (
6 |
7 |
8 | Home
9 | About
10 | Shop
11 | Jobs
12 | Partners
13 |
14 | © 2021 Radid Store Inc. All rights reserved
15 |
16 | )
17 | }
18 |
19 | export default Footer
20 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import NextDocument, { Html, Head, Main, NextScript } from "next/document";
2 | import { ColorModeScript } from "@chakra-ui/react";
3 | import favicon from "public/favicon.png";
4 | export default class Document extends NextDocument {
5 | render() {
6 | return (
7 |
8 |
9 |
10 |
11 |
15 |
16 |
17 | {/* Make Color mode to persists when you refresh the page. */}
18 |
19 |
20 |
21 |
22 |
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Hero from "@/components/Hero";
2 | import Products from "@/components/Product/Products";
3 | import commerce from "@/lib/commerce";
4 | import { motion } from "framer-motion";
5 | import React from "react";
6 |
7 |
8 | export async function getStaticProps() {
9 | const merchant = await commerce.merchants.about();
10 | const { data: categories } = await commerce.categories.list();
11 | const { data: products } = await commerce.products.list();
12 |
13 | return {
14 | props: {
15 | merchant,
16 | categories,
17 | products,
18 | },
19 | };
20 | }
21 |
22 | const Index = ({ merchant, categories, products }) => {
23 | return (
24 |
29 |
30 |
31 |
32 |
37 |
38 |
39 | );
40 | };
41 |
42 | export default Index;
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-chakra-ui",
3 | "version": "0.1.0",
4 | "scripts": {
5 | "dev": "next dev",
6 | "build": "next build",
7 | "start": "next start"
8 | },
9 | "dependencies": {
10 | "@chakra-ui/icons": "^1.0.0",
11 | "@chakra-ui/react": "^1.4.2",
12 | "@chec/commerce.js": "^2.4.1",
13 | "@chec/react-commercejs-hooks": "^0.3.1",
14 | "@emotion/react": "^11.0.0",
15 | "@emotion/styled": "^11.0.0",
16 | "@stripe/react-stripe-js": "^1.4.0",
17 | "@stripe/stripe-js": "^1.13.2",
18 | "framer-motion": "^4.0.3",
19 | "html-react-parser": "^1.2.6",
20 | "next": "latest",
21 | "next-images": "^1.7.0",
22 | "nprogress": "^0.2.0",
23 | "react": "^17.0.2",
24 | "react-dom": "^17.0.2",
25 | "react-hook-form": "^7.2.3",
26 | "react-icons": "^4.2.0",
27 | "react-id-swiper": "^4.0.0",
28 | "react-redux": "^7.2.3",
29 | "react-responsive": "^8.2.0",
30 | "react-scroll": "^1.8.2",
31 | "redux": "^4.0.5",
32 | "swiper": "^6.5.8",
33 | "swr": "^0.5.5",
34 | "uuid": "^8.3.2"
35 | },
36 | "license": "MIT"
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Harsimran Singh Barki
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 |
--------------------------------------------------------------------------------
/components/Shipping/Success.js:
--------------------------------------------------------------------------------
1 | import { Button } from "@chakra-ui/button";
2 | import { ArrowLeftIcon, ChevronLeftIcon } from "@chakra-ui/icons";
3 | import { Box, Divider, Grid, Heading, HStack, Text, VStack } from "@chakra-ui/layout";
4 | import { Spinner } from "@chakra-ui/spinner";
5 | import { chakra } from "@chakra-ui/system";
6 | import Link from "next/link";
7 | import React from "react";
8 |
9 | function Success({order}) {
10 |
11 | if (!order) {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 | )
19 | }
20 | return (
21 |
22 |
23 | Thank you for your purchase
24 |
25 | Order Ref: {''} {order?.customer_reference}
26 |
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | export default Success;
34 |
--------------------------------------------------------------------------------
/components/RelatedProducts.js:
--------------------------------------------------------------------------------
1 | import { Box, Flex, Heading, Text } from "@chakra-ui/layout";
2 | import { motion } from "framer-motion";
3 | import Product from "./Product/Product";
4 | import { v4 as uuidv4 } from "uuid";
5 |
6 | function RelatedProducts({ products }) {
7 | if (!products || products.length === 0) return null;
8 |
9 | return (
10 |
21 |
22 | Similar Products
23 |
24 | {products.map((product) => (
25 |
33 | ))}
34 |
35 |
36 |
37 | );
38 | }
39 |
40 | export default RelatedProducts;
41 |
--------------------------------------------------------------------------------
/components/VariantPicker.js:
--------------------------------------------------------------------------------
1 | import { Button, ButtonGroup } from "@chakra-ui/button";
2 | import { Box, Text } from "@chakra-ui/layout";
3 | import { Select } from "@chakra-ui/select";
4 | import React from "react";
5 | import { v4 as uuidv4 } from "uuid";
6 |
7 | function VariantPicker({ variantGroups = [], defaultValues = {}, ...props }) {
8 | if (!variantGroups || variantGroups.length === 0) return null;
9 |
10 | return (
11 | <>
12 | {variantGroups.map(({ options, ...group }) => (
13 |
14 |
15 | Choose Size
16 |
17 |
18 |
34 |
35 | ))}
36 | >
37 | );
38 | }
39 |
40 | export default VariantPicker;
41 |
--------------------------------------------------------------------------------
/components/SearchBar.js:
--------------------------------------------------------------------------------
1 | import { Box, Flex, Text } from "@chakra-ui/layout";
2 | import { Select } from "@chakra-ui/select";
3 | import React from "react";
4 |
5 | export const SearchBar = ({ categories }) => {
6 | return (
7 |
8 |
16 |
17 |
18 | Brand
19 |
20 |
25 |
26 |
27 |
28 | Price
29 |
30 |
35 |
36 |
37 |
38 | Filter
39 |
40 |
45 |
46 |
47 |
48 | );
49 | };
50 |
--------------------------------------------------------------------------------
/styles/theme.js:
--------------------------------------------------------------------------------
1 | import { extendTheme } from "@chakra-ui/react";
2 | import { createBreakpoints } from "@chakra-ui/theme-tools";
3 |
4 | const fonts = { mono: `'Menlo', monospace` };
5 |
6 | const breakpoints = createBreakpoints({
7 | sm: "40em",
8 | md: "52em",
9 | lg: "64em",
10 | xl: "80em",
11 | });
12 |
13 | import { mode } from "@chakra-ui/theme-tools";
14 | const theme = extendTheme({
15 | colors: {
16 | black: "#000000",
17 | },
18 | fonts: {
19 | heading: "Playfair Display, serif",
20 | body: "Source Sans Pro, sans-serif",
21 | },
22 | fontWeights: {
23 | light: 300,
24 | normal: 400,
25 | medium: 500,
26 | semiBold: 600,
27 | bold: 700,
28 | boldest: 800,
29 | },
30 | breakpoints,
31 | icons: {
32 | logo: {
33 | path: (
34 |
47 | ),
48 | viewBox: "0 0 3000 3163",
49 | },
50 | },
51 | styles: {
52 | global: (props) => ({
53 | body: {
54 | color: mode("#01020A", "#FAFAFA")(props),
55 | bg: mode("white", "#272343")(props),
56 | },
57 | }),
58 | },
59 | });
60 |
61 | export default theme;
62 |
--------------------------------------------------------------------------------
/components/Hero.js:
--------------------------------------------------------------------------------
1 | import { Box, Flex, Heading, Text } from "@chakra-ui/layout";
2 | import { chakra } from "@chakra-ui/system";
3 | import React from "react";
4 | import heroBg from 'public/hero.jpg'
5 | import { motion } from "framer-motion";
6 |
7 | function Hero() {
8 | const MotionText = motion(Text)
9 | const MotionHeading = motion(Heading)
10 | return (
11 |
14 |
15 |
24 |
25 |
33 | Rapid Store
34 |
35 |
44 | Hottest Shoes Collection With {""}
45 |
46 | Rapid 1 Day Delivery
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | );
56 | }
57 |
58 | export default Hero;
59 |
--------------------------------------------------------------------------------
/components/Shipping/OrderTotal.js:
--------------------------------------------------------------------------------
1 | import { useCartState } from "@/context/cart";
2 | import { Flex, VStack } from "@chakra-ui/layout";
3 | import { chakra } from "@chakra-ui/system";
4 | import React from "react";
5 |
6 | function OrderTotal() {
7 | const { line_items, subtotal, total_unique_items } = useCartState();
8 | return (
9 |
10 | {line_items?.map((product) => {
11 | return (
12 |
19 |
20 | {product?.name}
21 |
22 |
23 | {product?.price.formatted} x{" "}
24 |
25 | {product?.quantity}
26 |
27 |
28 |
29 | );
30 | })}
31 |
32 |
38 |
39 | Total Items:
40 |
41 | {total_unique_items}
42 |
43 |
49 |
50 | Total Amount:
51 |
52 | {subtotal?.formatted_with_symbol}
53 |
54 |
55 | );
56 | }
57 |
58 | export default OrderTotal;
59 |
--------------------------------------------------------------------------------
/components/NavBar.js:
--------------------------------------------------------------------------------
1 | import { useCartState } from "@/context/cart";
2 | import { Box, Container, Flex, HStack } from "@chakra-ui/layout";
3 | import { chakra } from "@chakra-ui/system";
4 | import Link from "next/link";
5 | import React from "react";
6 | import { FiShoppingBag, FiShoppingCart } from "react-icons/fi";
7 |
8 | function NavBar() {
9 | const { line_items, subtotal, total_unique_items } = useCartState();
10 | return (
11 | <>
12 |
20 |
21 |
22 |
23 |
24 |
25 | Rapid Store
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {total_unique_items == 0 ? null : (
34 |
45 | {total_unique_items}
46 |
47 | )}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | >
57 | );
58 | }
59 |
60 | export default NavBar;
61 |
--------------------------------------------------------------------------------
/components/Product/Products.js:
--------------------------------------------------------------------------------
1 | import { Box, Container, Flex, Grid, Heading } from "@chakra-ui/layout";
2 | import { motion } from "framer-motion";
3 | import React from "react";
4 | import Product from "./Product";
5 | import { v4 as uuidv4 } from "uuid";
6 |
7 | function Products({ merchant, categories, products }) {
8 |
9 | const easing = [0.6, -0.05, 0.01, 0.99];
10 | const container = {
11 | hidden: { y: -10 },
12 | show: {
13 | y: 0,
14 | transition: {
15 | staggerChildren: 0.2,
16 | ease: easing,
17 | },
18 | },
19 | };
20 |
21 | const listItem = {
22 | hidden: { opacity: 0, y: -10 },
23 | show: { opacity: 1, y: 0 },
24 | };
25 |
26 | const MotionGrid = motion(Grid);
27 | return (
28 |
29 |
30 | Top Sellers
31 |
42 | {products.map((product) => {
43 | return (
44 |
45 | {" "}
54 |
55 | );
56 | })}
57 |
58 |
59 |
60 | );
61 | }
62 |
63 | export default Products;
64 |
--------------------------------------------------------------------------------
/context/cart.js:
--------------------------------------------------------------------------------
1 | import { createContext, useReducer, useEffect, useContext } from "react";
2 | import { useCycle } from "framer-motion";
3 | import commerce from "@/lib/commerce";
4 |
5 | const CartStateContext = createContext();
6 | const CartDispatchContext = createContext();
7 |
8 | const SET_CART = "SET_CART";
9 | const RESET = "RESET";
10 |
11 | const initialState = {
12 | total_items: 0,
13 | total_unique_items: 0,
14 | line_items: [],
15 | };
16 |
17 | const reducer = (state, action) => {
18 | switch (action.type) {
19 | case SET_CART:
20 | return { ...state, ...action.payload };
21 | case RESET:
22 | return initialState;
23 | default:
24 | throw new Error(`Unknown action: ${action.type}`);
25 | }
26 | };
27 |
28 | export const CartProvider = ({ children }) => {
29 | const [open, toggle] = useCycle(false, true);
30 | const [state, dispatch] = useReducer(reducer, initialState);
31 |
32 | useEffect(() => {
33 | getCart();
34 | }, []);
35 |
36 | const getCart = async () => {
37 | try {
38 | const cart = await commerce.cart.retrieve();
39 |
40 | dispatch({ type: SET_CART, payload: cart });
41 | } catch (err) {
42 | // noop
43 | }
44 | };
45 |
46 | const setCart = async (payload) => dispatch({ type: SET_CART, payload });
47 |
48 | const showCart = () => {
49 | toggle();
50 | document.body.classList.add("overflow-hidden");
51 | };
52 |
53 | const closeCart = () => {
54 | toggle();
55 | document.body.classList.remove("overflow-hidden");
56 | };
57 |
58 | const reset = async () => dispatch({ type: RESET });
59 |
60 | return (
61 |
64 |
65 | {children}
66 |
67 |
68 | );
69 | };
70 |
71 | export const useCartState = () => useContext(CartStateContext);
72 | export const useCartDispatch = () => useContext(CartDispatchContext);
73 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import { ChakraProvider, ColorModeProvider, CSSReset } from "@chakra-ui/react";
2 | import theme from "@/styles/theme";
3 | import NavBar from "@/components/NavBar";
4 |
5 | import Head from "next/head";
6 | import { css, Global } from "@emotion/react";
7 | import { CartProvider } from "@/context/cart";
8 | import NProgress from "nprogress";
9 | import "nprogress/nprogress.css";
10 | import { Router } from "next/router";
11 | import { CheckoutProvider } from "@/context/checkout";
12 | import { AnimatePresence } from "framer-motion";
13 | import Footer from "@/components/Footer";
14 |
15 | NProgress.configure({
16 | showSpinner: true,
17 | trickleRate: 0.1,
18 | trickleSpeed: 300,
19 | });
20 |
21 | Router.events.on("routeChangeStart", () => {
22 | NProgress.start();
23 | });
24 |
25 | Router.events.on("routeChangeComplete", () => {
26 | NProgress.done();
27 | });
28 |
29 | Router.events.on("routeChangeError", () => {
30 | NProgress.done();
31 | });
32 |
33 | const GlobalStyle = ({ children }) => {
34 | return (
35 | <>
36 |
37 |
38 | Rapid Store
39 |
40 |
41 |
53 | {children}
54 | >
55 | );
56 | };
57 |
58 | function App({ Component, pageProps }) {
59 | return (
60 |
61 |
62 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | );
80 | }
81 |
82 | export default App;
83 |
--------------------------------------------------------------------------------
/components/Product/Product.js:
--------------------------------------------------------------------------------
1 | import { useCartDispatch } from "@/context/cart";
2 | import commerce from "@/lib/commerce";
3 | import { Box, Text } from "@chakra-ui/layout";
4 | import { useToast } from "@chakra-ui/toast";
5 | import { motion } from "framer-motion";
6 | import Image from "next/image";
7 | import Link from "next/link";
8 | import React, { useState } from "react";
9 |
10 | import { v4 as uuidv4 } from "uuid";
11 |
12 |
13 | function Product({ id, name, description, price, image, permalink }) {
14 | const { setCart } = useCartDispatch();
15 | const toast = useToast();
16 | const [loading, setLoading] = useState(false);
17 | const MotionBox = motion(Box);
18 | const addToCart = (name, productId, qty) => {
19 | setLoading(true);
20 | commerce.cart
21 | .add(productId, qty)
22 | .then(({ cart }) => {
23 | setCart(cart);
24 | return cart;
25 | })
26 | .then(() => {
27 | setLoading(false);
28 | toast({
29 | title: `"${name}" has been added to your cart`,
30 | description: `Your quantity ${qty}`,
31 | status: "success",
32 | duration: 9000,
33 | isClosable: true,
34 | });
35 | })
36 | .catch((error) => {
37 | setLoading(false);
38 | toast({
39 | title: error.message,
40 | description: `Something happend try again`,
41 | status: "error",
42 | duration: 9000,
43 | isClosable: true,
44 | });
45 | });
46 | };
47 | return (
48 |
49 |
50 |
73 |
74 |
75 |
76 |
77 |
80 |
87 | {name}
88 |
89 |
90 |
91 | {price}
92 |
93 |
94 |
95 |
96 |
97 | );
98 | }
99 |
100 | export default Product;
101 |
--------------------------------------------------------------------------------
/components/Product/ProductButton.js:
--------------------------------------------------------------------------------
1 | import { Button, IconButton } from "@chakra-ui/button";
2 | import { Flex, HStack, Text, toast } from "@chakra-ui/layout";
3 | import React, { useState } from "react";
4 | import { useCartDispatch, useCartState } from "@/context/cart";
5 | import { useToast } from "@chakra-ui/toast";
6 | import { AddIcon, MinusIcon } from "@chakra-ui/icons";
7 | import commerce from "@/lib/commerce";
8 | import { MdDelete } from "react-icons/md";
9 |
10 | function ProductButton({ name, id, quantity }) {
11 | const toast = useToast();
12 | const { setCart } = useCartDispatch();
13 | const [loading, setLoading] = useState(false);
14 | const handleUpdateCart = ({ cart }) => {
15 | setCart(cart);
16 |
17 | return cart;
18 | };
19 |
20 | const handleRemoveItem = () => {
21 | setLoading(true);
22 | commerce.cart
23 | .remove(id)
24 | .then(handleUpdateCart)
25 | .then(({ subtotal }) => {
26 | toast({
27 | title: `${name} has been removed from your cart`,
28 | description: `Your new subtotal is now ${subtotal.formatted_with_symbol}`,
29 | status: "success",
30 | duration: 9000,
31 | isClosable: true,
32 | });
33 | });
34 | };
35 |
36 | const decrementQuantity = () => {
37 | quantity > 1
38 | ? commerce.cart
39 | .update(id, { quantity: quantity - 1 })
40 | .then(handleUpdateCart)
41 | .then(({ subtotal }) =>
42 | toast({
43 | title: `"${name}" has been removed from your cart`,
44 | description: `Your new subtotal is now ${subtotal.formatted_with_symbol}`,
45 | status: "success",
46 | duration: 9000,
47 | isClosable: true,
48 | })
49 | )
50 | : handleRemoveItem();
51 | };
52 | const incrementQuantity = () => {
53 | commerce.cart
54 | .update(id, { quantity: quantity + 1 })
55 | .then(handleUpdateCart)
56 | .then(({ subtotal }) =>
57 | toast({
58 | title: `Another "${name}" has been added to your cart`,
59 | description: `Your new subtotal is now ${subtotal.formatted_with_symbol}`,
60 | status: "success",
61 | duration: 9000,
62 | isClosable: true,
63 | })
64 | );
65 | };
66 | return (
67 |
68 |
69 | incrementQuantity()}
76 | isDisabled={loading}
77 | icon={}
78 | />
79 | {quantity}
80 | decrementQuantity()}
86 | aria-label="Remove Item"
87 | isDisabled={loading}
88 | icon={}
89 | />
90 |
91 | }
94 | size="sm"
95 | colorScheme="red"
96 | onClick={() => handleRemoveItem()}
97 | >
98 | Remove
99 |
100 |
101 | );
102 | }
103 |
104 | export default ProductButton;
105 |
--------------------------------------------------------------------------------
/context/checkout.js:
--------------------------------------------------------------------------------
1 | import { createContext, useReducer, useContext } from "react";
2 |
3 | import commerce from "@/lib/commerce";
4 |
5 | const CheckoutStateContext = createContext();
6 | const CheckoutDispatchContext = createContext();
7 |
8 | const SET_CURRENT_STEP = "SET_CURRENT_STEP";
9 | const SET_CHECKOUT = "SET_CHECKOUT";
10 | const SET_LIVE = "SET_LIVE";
11 | const SET_PROCESSING = "SET_PROCESSING";
12 | const SET_ERROR = "SET_ERROR";
13 | const RESET = "RESET";
14 |
15 | const initialState = {
16 | currentStep: "extrafields",
17 | processing: false,
18 | error: null,
19 | };
20 |
21 | const reducer = (state, action) => {
22 | switch (action.type) {
23 | case SET_CURRENT_STEP:
24 | return {
25 | ...state,
26 | currentStep: action.payload,
27 | };
28 | case SET_CHECKOUT:
29 | return {
30 | ...state,
31 | ...action.payload,
32 | };
33 | case SET_LIVE:
34 | return { ...state, live: { ...state.live, ...action.payload } };
35 | case SET_PROCESSING:
36 | return { ...state, processing: action.payload };
37 | case SET_ERROR:
38 | return { ...state, error: action.payload };
39 | case RESET:
40 | return initialState;
41 | default:
42 | throw new Error(`Unknown action: ${action.type}`);
43 | }
44 | };
45 |
46 | export const CheckoutProvider = ({ children }) => {
47 | const [state, dispatch] = useReducer(reducer, initialState);
48 |
49 | const generateToken = async (cartId) => {
50 | if (!cartId) return;
51 |
52 | try {
53 | const payload = await commerce.checkout.generateToken(cartId, {
54 | type: "cart",
55 | });
56 |
57 | dispatch({ type: SET_CHECKOUT, payload });
58 | } catch (err) {
59 | // noop
60 |
61 | }
62 | };
63 |
64 | const setShippingMethod = async (shipping_option_id, country, region) => {
65 | try {
66 | const { live } = await commerce.checkout.checkShippingOption(state.id, {
67 | shipping_option_id,
68 | country,
69 | ...(region && { region }),
70 | });
71 |
72 | dispatch({ type: SET_LIVE, payload: live });
73 | } catch (err) {
74 | // noop
75 | }
76 | };
77 |
78 | const handleCaptureCheckout = async (checkoutTokenId, newOrder) => {
79 | try {
80 | const incomingOrder = await commerce.checkout.capture(
81 | checkoutTokenId,
82 | newOrder
83 | );
84 |
85 |
86 | reset;
87 | } catch (error) {}
88 | };
89 |
90 | const setCurrentStep = (step) =>
91 | dispatch({ type: SET_CURRENT_STEP, payload: step });
92 |
93 | const nextStepFrom = (currentStep) => {
94 | switch (currentStep) {
95 | case "extrafields":
96 | return state.collects.shipping_address ? "shipping" : "billing";
97 | case "shipping":
98 | default:
99 | return "billing";
100 | }
101 | };
102 |
103 | const capture = (values) => commerce.checkout.capture(state.id, values);
104 |
105 | const setProcessing = (payload) =>
106 | dispatch({ type: SET_PROCESSING, payload });
107 |
108 | const setError = (payload) => dispatch({ type: SET_ERROR, payload });
109 |
110 | const reset = () => dispatch({ type: RESET });
111 |
112 | return (
113 |
126 |
127 | {children}
128 |
129 |
130 | );
131 | };
132 |
133 | export const useCheckoutState = () => useContext(CheckoutStateContext);
134 | export const useCheckoutDispatch = () => useContext(CheckoutDispatchContext);
135 |
--------------------------------------------------------------------------------
/components/Shipping/Payment.js:
--------------------------------------------------------------------------------
1 | import { useCartState } from "@/context/cart";
2 | import { useCheckoutDispatch, useCheckoutState } from "@/context/checkout";
3 | import { Button, ButtonGroup } from "@chakra-ui/button";
4 | import { ArrowBackIcon } from "@chakra-ui/icons";
5 | import { Box, Divider, Flex, Text } from "@chakra-ui/layout";
6 | import {
7 | CardElement,
8 | Elements,
9 | ElementsConsumer,
10 | } from "@stripe/react-stripe-js";
11 | import { loadStripe } from "@stripe/stripe-js";
12 | import { useRouter } from "next/router";
13 | import React, { useState } from "react";
14 | import OrderTotal from "./OrderTotal";
15 | import commerce from "@/lib/commerce";
16 |
17 | function Payment({ checkoutToken, setCurrentStep, shippingData,setOrderRescipt }) {
18 | const [order,setOrder] = useState()
19 | const stripePromise = loadStripe(
20 | `pk_test_51HRLfcGvtASXO12RzHg5XDVUuA04VJnfjXFappeXC5ZsXvnIFrbVsEl9K4qb6uwaZGcRD4Yao3S99bERNQrfJiGf00m4meZfoa`
21 | );
22 | const { reset } = useCheckoutDispatch();
23 |
24 | const handleCaptureCheckout = async (checkoutTokenId, newOrder) => {
25 | try {
26 | const incomingOrder = await commerce.checkout.capture(
27 | checkoutTokenId,
28 | newOrder
29 | );
30 | setOrderRescipt(incomingOrder)
31 |
32 | reset;
33 | } catch (error) {
34 |
35 | }
36 | };
37 | const handleSubmit = async (e, elements, stripe) => {
38 | e.preventDefault();
39 |
40 | if (!stripe || !elements) return;
41 |
42 | const cardEle = elements.getElement(CardElement);
43 | const { error, paymentMethod } = await stripe.createPaymentMethod({
44 | type: "card",
45 | card: cardEle,
46 | });
47 |
48 | if (error) {
49 |
50 | } else {
51 | const orderData = {
52 | line_items: checkoutToken.live.line_items,
53 | customer: {
54 | firstname: shippingData.firstName,
55 | lastname: shippingData.lastName,
56 | email: shippingData.email,
57 | },
58 | shipping: {
59 | name: shippingData.name,
60 | street: shippingData.address,
61 | town_city: 'San Francisco',
62 | county_state: 'US-CA',
63 | postal_zip_code: '94103',
64 | country: 'US'
65 | },
66 |
67 | fullfillment: { shippingMethod: "Domestic" },
68 | payment: {
69 | gateway: "stripe",
70 | stripe: {
71 | payment_method_id: paymentMethod.id,
72 | },
73 | },
74 | };
75 | handleCaptureCheckout(checkoutToken.id, orderData);
76 | setCurrentStep("success");
77 | }
78 | };
79 | return (
80 |
81 |
82 |
83 |
84 |
85 | {({ elements, stripe }) => (
86 |
116 | )}
117 |
118 |
119 |
120 | );
121 | }
122 |
123 | export default Payment;
124 |
--------------------------------------------------------------------------------
/components/Shipping/Shipping.js:
--------------------------------------------------------------------------------
1 | import { Button } from "@chakra-ui/button";
2 | import { FormControl } from "@chakra-ui/form-control";
3 | import { Input } from "@chakra-ui/input";
4 | import { HStack, Stack, VStack } from "@chakra-ui/layout";
5 | import { Select } from "@chakra-ui/select";
6 | import HTMLReactParser from "html-react-parser";
7 | import React from "react";
8 |
9 | import { useForm } from "react-hook-form";
10 |
11 | function Shipping({
12 | countries,
13 | subdivisions,
14 | setShippingData,
15 | checkoutId,
16 | setCurrentStep,
17 | setShippingValue,
18 | }) {
19 | const {
20 | register,
21 | handleSubmit,
22 | watch,
23 | formState: { errors },
24 | } = useForm();
25 | const onSubmit = async (data) => {
26 | setCurrentStep("payment");
27 | setShippingData(data);
28 | };
29 | return (
30 |
31 |
32 |
33 |
41 |
42 |
43 |
50 |
51 |
52 |
53 |
60 |
61 |
62 |
69 |
70 |
71 | {" "}
72 |
73 |
80 |
81 |
82 |
89 |
90 |
91 |
92 |
93 |
94 |
108 |
109 |
110 |
117 |
118 |
119 |
128 |
129 | );
130 | }
131 |
132 | export default Shipping;
133 |
--------------------------------------------------------------------------------
/pages/cart.js:
--------------------------------------------------------------------------------
1 | import ProductButton from "@/components/Product/ProductButton";
2 | import { useCartDispatch, useCartState } from "@/context/cart";
3 | import { Button } from "@chakra-ui/button";
4 | import {
5 | Box,
6 | Container,
7 | Divider,
8 | Flex,
9 | Grid,
10 | Heading,
11 | Text,
12 | VStack,
13 | } from "@chakra-ui/layout";
14 | import { Spinner } from "@chakra-ui/spinner";
15 | import { chakra } from "@chakra-ui/system";
16 | import { useToast } from "@chakra-ui/toast";
17 | import { motion } from "framer-motion";
18 | import Image from "next/image";
19 | import Link from "next/link";
20 | import React from "react";
21 | import { useMediaQuery } from "react-responsive";
22 | import { Swiper, SwiperSlide } from "swiper/react";
23 | import "swiper/swiper-bundle.css";
24 |
25 | function cart() {
26 | const { line_items, subtotal, total_unique_items } = useCartState();
27 | const isTabletOrMobile = useMediaQuery({ query: "(max-width: 1224px)" });
28 | const { setCart } = useCartDispatch();
29 | const toast = useToast();
30 | const isEmpty = line_items.length === 0;
31 |
32 | if (subtotal == undefined) {
33 | return (
34 |
35 |
36 |
37 |
38 |
39 | );
40 | }
41 | if (isEmpty) {
42 | return (
43 |
44 | Your Cart Is Empty
45 |
46 |
49 |
50 |
51 | );
52 | }
53 |
54 | const MotionFlex = motion(Flex);
55 |
56 | return (
57 |
62 |
63 |
64 | Your Cart
65 |
66 |
67 | {line_items?.map((product) => {
68 | return (
69 |
70 |
84 |
85 |
91 |
96 |
97 |
98 |
99 |
100 |
105 | {product.name}
106 |
107 |
108 |
109 | {product.price.formatted_with_symbol}
110 |
111 |
112 |
117 |
118 |
119 |
120 | );
121 | })}
122 |
123 |
124 |
125 |
126 |
136 | Order Summary
137 |
138 |
139 |
140 | {line_items?.map((product) => {
141 | return (
142 |
149 |
150 | {product.name}
151 |
152 |
153 | {product.price.formatted} x {product.quantity}
154 |
155 |
156 | );
157 | })}
158 |
159 |
165 |
166 | Total Items:
167 |
168 | {total_unique_items}
169 |
170 |
176 |
177 | Total Amount:
178 |
179 | {subtotal.formatted_with_symbol}
180 |
181 |
182 |
183 |
186 |
187 |
188 |
189 |
190 | );
191 | }
192 |
193 | export default cart;
194 |
--------------------------------------------------------------------------------
/pages/checkout.js:
--------------------------------------------------------------------------------
1 | import OrderTotal from "@/components/Shipping/OrderTotal";
2 | import Payment from "@/components/Shipping/Payment";
3 | import Shipping from "@/components/Shipping/Shipping";
4 | import Success from "@/components/Shipping/Success";
5 | import { useCartDispatch, useCartState } from "@/context/cart";
6 | import commerce from "@/lib/commerce";
7 | import {
8 | Breadcrumb,
9 | BreadcrumbItem,
10 | BreadcrumbLink
11 | } from "@chakra-ui/breadcrumb";
12 | import { ChevronRightIcon } from "@chakra-ui/icons";
13 | import {
14 | Box,
15 | Divider,
16 | Flex,
17 | Grid,
18 | Heading
19 | } from "@chakra-ui/layout";
20 | import { Spinner } from "@chakra-ui/spinner";
21 | import { AnimatePresence, motion } from "framer-motion";
22 | import { useRouter } from "next/router";
23 | import React, { useEffect, useState } from "react";
24 |
25 |
26 | function checkout() {
27 | const [countries, setCountries] = useState();
28 | const [orderRecipe, setOrderRescipt] = useState();
29 | const [subdivisions, setSubdivisions] = useState();
30 | const { line_items, subtotal, total_unique_items } = useCartState();
31 | const { getCart } = useCartDispatch();
32 | const [checkoutId, setCheckoutId] = useState();
33 | const [shippigOptions, setShippingOptions] = useState();
34 | const [checkoutToken, setCheckoutToken] = useState();
35 | const [currentStep, setCurrentStep] = useState("shipping");
36 | const [shippingData, setShippingData] = useState();
37 | const router = useRouter();
38 |
39 | useEffect(() => {
40 | getCartInfo();
41 | fetchCountries(checkoutId);
42 | fetchSubdivisions(checkoutId, "AF");
43 | shippingOption(checkoutId, "AF");
44 | }, []);
45 |
46 | const fetchSubdivisions = async (countryCode) => {
47 | try {
48 | const res = await commerce.services.localeListSubdivisions(countryCode);
49 |
50 | let lol = Object.entries(res.subdivisions).map(reducer);
51 | setSubdivisions(res.html);
52 | } catch (err) {
53 | // noop
54 | }
55 | };
56 | const reducer = ([code, name]) => ({
57 | value: code,
58 | label: name,
59 | });
60 | const fetchCountries = async (checkoutId) => {
61 | try {
62 | const { countries } = await commerce.services.localeListCountries(
63 | checkoutId
64 | );
65 | let res = Object.entries(countries).map(reducer);
66 | setCountries(res);
67 | } catch (err) {
68 | // noop
69 | }
70 | };
71 |
72 | const shippingOption = async (checkoutId, country, region = null) => {
73 | try {
74 | const options = await commerce.checkout.getShippingOptions(checkoutId, {
75 | country: "US",
76 | region: "CA",
77 | });
78 | setShippingOptions(options);
79 | } catch (err) {}
80 | };
81 |
82 | const setShippingValue = (checkoutId, country, region = null) => {
83 | fetchSubdivisions(country);
84 | shippingOption(checkoutId, country, region);
85 | };
86 |
87 | const getCartInfo = async () => {
88 | const cart = await commerce.cart.retrieve();
89 | const res = await commerce.checkout.generateToken(cart.id, {
90 | type: "cart",
91 | });
92 | setCheckoutToken(res);
93 | setCheckoutId(res.id);
94 | };
95 |
96 | if (!checkoutId) {
97 | return (
98 |
99 |
100 |
101 |
102 | );
103 | }
104 |
105 | return (
106 |
107 |
108 |
109 | Checkout
110 |
111 | }
116 | >
117 |
118 |
124 | Shipping
125 |
126 |
127 |
128 |
129 |
135 | Payment
136 |
137 |
138 |
139 |
140 |
141 | {currentStep == "shipping" && (
142 |
154 |
163 |
164 | )}
165 |
166 |
167 | {currentStep == "payment" && (
168 |
179 | {" "}
180 |
186 |
187 | )}
188 |
189 |
190 | {currentStep == "success" && (
191 |
202 |
203 |
204 | )}
205 |
206 |
207 | {currentStep == "shipping" && (
208 |
217 | Order Summary
218 |
219 |
220 |
221 | )}
222 |
223 |
224 |
225 |
226 |
227 |
228 | );
229 | }
230 |
231 | export default checkout;
232 |
--------------------------------------------------------------------------------
/pages/product/[permalink].js:
--------------------------------------------------------------------------------
1 | import RelatedProducts from "@/components/RelatedProducts";
2 | import VariantPicker from "@/components/VariantPicker";
3 | import { useCartDispatch } from "@/context/cart";
4 | import commerce from "@/lib/commerce";
5 | import { Button } from "@chakra-ui/button";
6 | import { Badge, Box, Divider, Flex, Grid, Text } from "@chakra-ui/layout";
7 | import { chakra } from "@chakra-ui/system";
8 | import { useToast } from "@chakra-ui/toast";
9 | import { motion } from "framer-motion";
10 | import HTMLReactParser from "html-react-parser";
11 | import Head from "next/head";
12 | import Image from "next/image";
13 | import React, { useState } from "react";
14 | import { FiShoppingBag } from "react-icons/fi";
15 | import { Swiper, SwiperSlide } from "swiper/react";
16 | import SwiperCore, { Navigation, Pagination, Scrollbar, A11y } from "swiper";
17 | import "swiper/swiper-bundle.css";
18 | import { useMediaQuery } from "react-responsive";
19 |
20 | export async function getStaticProps({ params }) {
21 | const { permalink } = params;
22 |
23 | const product = await commerce.products.retrieve(permalink, {
24 | type: "permalink",
25 | });
26 |
27 | return {
28 | props: {
29 | product,
30 | },
31 | revalidate: 60,
32 | };
33 | }
34 |
35 | export async function getStaticPaths() {
36 | const { data: products } = await commerce.products.list();
37 |
38 | return {
39 | paths: products.map(({ permalink }) => ({
40 | params: {
41 | permalink,
42 | },
43 | })),
44 | fallback: false,
45 | };
46 | }
47 |
48 | const Product = ({ product }) => {
49 | const isTabletOrMobile = useMediaQuery({ query: "(max-width: 1224px)" });
50 | const toast = useToast();
51 | const { setCart } = useCartDispatch();
52 | const [loading, setLoading] = useState(false);
53 |
54 | const {
55 | variant_groups: variantGroups,
56 | assets,
57 | meta,
58 | related_products: relatedProducts,
59 | } = product;
60 | const images = assets.filter(({ is_image }) => is_image);
61 |
62 | const initialVariants = React.useMemo(
63 | () =>
64 | variantGroups.reduce((all, { id, options }) => {
65 | const [firstOption] = options;
66 |
67 | return { ...all, [id]: firstOption.id };
68 | }, {}),
69 | [product.permalink]
70 | );
71 |
72 | const [selectedVariants, setSelectedVariants] = React.useState(
73 | initialVariants
74 | );
75 | SwiperCore.use([Navigation, Pagination, Scrollbar, A11y]);
76 | React.useEffect(() => {
77 | setSelectedVariants(initialVariants);
78 | }, [product.permalink]);
79 |
80 | const handleVariantChange = ({ target: { id, value } }) =>
81 | setSelectedVariants({
82 | ...selectedVariants,
83 | [id]: value,
84 | });
85 |
86 | const addToCart = () => {
87 | setLoading(true);
88 | commerce.cart
89 | .add(product.id, 1, selectedVariants)
90 | .then(({ cart }) => {
91 | setCart(cart);
92 | return cart;
93 | })
94 | .then(({ subtotal }) => {
95 | setLoading(false);
96 | toast({
97 | title: `${product.name} is now in your cart.`,
98 | description: ` Your subtotal is now ${subtotal.formatted_with_symbol}`,
99 | status: "success",
100 | duration: 9000,
101 | isClosable: true,
102 | });
103 | })
104 | .catch(() => {
105 | setLoading(false);
106 | toast({
107 | title: "Please try again",
108 | description: `Something happend try again`,
109 | status: "error",
110 | duration: 9000,
111 | isClosable: true,
112 | });
113 | });
114 | };
115 | const MotionGrid = motion(Grid);
116 | const MotionBox = motion(Box);
117 | const MotionText = motion(Text);
118 | return (
119 |
124 |
125 | {product.name}
126 |
127 |
128 |
141 | {product.name}
142 |
143 |
156 |
157 | {product.is.sold_out ? (
158 | Sold Out
159 | ) : (
160 | In Stock
161 | )}
162 |
163 |
164 |
172 |
183 |
189 | {product.assets.map((media) => {
190 | return (
191 |
192 |
204 | {" "}
205 |
206 |
207 | );
208 | })}
209 |
210 |
211 |
212 |
225 |
231 |
232 |
233 |
234 |
235 | {product.price.formatted_with_symbol}
236 |
237 |
238 |
243 |
250 | {HTMLReactParser(product.description)}
251 |
252 |
253 | }
258 | colorScheme="teal"
259 | isLoading={loading}
260 | textColor="gray.100"
261 | rounded="lg"
262 | >
263 | Add to Bag
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 | );
278 | };
279 |
280 | export default Product;
281 |
--------------------------------------------------------------------------------
/public/hero.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------