├── public
├── robots.txt
├── favicon.ico
├── favicon-16x16.png
├── favicon-32x32.png
├── maskable_icon.png
├── apple-touch-icon.png
├── android-chrome-192x192.png
└── android-chrome-512x512.png
├── src
├── assets
│ ├── Blog1.webp
│ ├── Blog2.webp
│ ├── Blog3.webp
│ ├── light1.webp
│ ├── light2.webp
│ ├── instaGram1.webp
│ ├── instaGram2.webp
│ ├── instaGram3.webp
│ ├── instaGram4.webp
│ ├── basketChair.webp
│ ├── undraw_search_engine.svg
│ └── undraw_empty_cart.svg
├── components
│ ├── Error.jsx
│ ├── Loading.jsx
│ ├── ProductsHome.jsx
│ ├── Logo.jsx
│ ├── View_button.jsx
│ ├── Breadcrumb.jsx
│ ├── Product_title.jsx
│ ├── ProductImage.jsx
│ ├── AllProducts.jsx
│ ├── Grid_view_products.jsx
│ ├── ProductGrid.jsx
│ ├── CollectionProducts.jsx
│ ├── Empty_cart.jsx
│ ├── List_view_products.jsx
│ ├── NotFound.jsx
│ ├── ProductCategory.jsx
│ ├── Stars.jsx
│ ├── Basket.jsx
│ ├── Header.jsx
│ ├── Stripe_Checkout.jsx
│ ├── Instagram.jsx
│ ├── ProductImages.jsx
│ ├── Cart_Button.jsx
│ ├── CategoryProducts.jsx
│ ├── Navbar.jsx
│ ├── Sidebar.jsx
│ ├── index.jsx
│ ├── Sort.jsx
│ ├── Order_summary.jsx
│ ├── CheckoutForm.jsx
│ ├── HomeProduct.jsx
│ ├── AddToCart.jsx
│ ├── Footer.jsx
│ ├── Filters.jsx
│ ├── CartItems.jsx
│ └── Sidebar_Filter.jsx
├── pages
│ ├── Checkout.jsx
│ ├── News.jsx
│ ├── Features.jsx
│ ├── Services.jsx
│ ├── ProtectedRoute.jsx
│ ├── Cart.jsx
│ ├── index.jsx
│ ├── Error.jsx
│ ├── Home.jsx
│ ├── Products.jsx
│ ├── Completion.jsx
│ ├── SingleProduct.jsx
│ └── ui-script.js
├── utils
│ ├── helper.jsx
│ └── constants.jsx
├── index.css
├── main.jsx
├── actions
│ └── actions.jsx
├── App.jsx
├── context
│ ├── cart
│ │ └── cart_context.jsx
│ ├── product
│ │ └── products_context.jsx
│ └── filter
│ │ └── filter_context.jsx
├── reducers
│ ├── product
│ │ └── products_reducer.jsx
│ ├── cart
│ │ └── cart_reducer.jsx
│ └── filter
│ │ └── filter_reducer.jsx
└── app.css
├── postcss.config.cjs
├── dev-dist
├── registerSW.js
├── sw.js
├── workbox-7369c0e1.js
├── workbox-3625d7b0.js
├── workbox-519d0965.js
└── workbox-68741b2f.js
├── netlify.toml
├── .gitignore
├── tailwind.config.cjs
├── functions
├── single-product.js
├── stripe-payment-intent.js
└── products.js
├── LICENSE
├── package.json
├── vite.config.js
├── index.html
└── README.MD
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/Blog1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/src/assets/Blog1.webp
--------------------------------------------------------------------------------
/src/assets/Blog2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/src/assets/Blog2.webp
--------------------------------------------------------------------------------
/src/assets/Blog3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/src/assets/Blog3.webp
--------------------------------------------------------------------------------
/src/assets/light1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/src/assets/light1.webp
--------------------------------------------------------------------------------
/src/assets/light2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/src/assets/light2.webp
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/maskable_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/public/maskable_icon.png
--------------------------------------------------------------------------------
/src/assets/instaGram1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/src/assets/instaGram1.webp
--------------------------------------------------------------------------------
/src/assets/instaGram2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/src/assets/instaGram2.webp
--------------------------------------------------------------------------------
/src/assets/instaGram3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/src/assets/instaGram3.webp
--------------------------------------------------------------------------------
/src/assets/instaGram4.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/src/assets/instaGram4.webp
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/src/assets/basketChair.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/src/assets/basketChair.webp
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/galaxyxyz5/E-Commerce-Web/HEAD/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/dev-dist/registerSW.js:
--------------------------------------------------------------------------------
1 | if('serviceWorker' in navigator) navigator.serviceWorker.register('/dev-sw.js?dev-sw', { scope: '/', type: 'classic' })
--------------------------------------------------------------------------------
/src/components/Error.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | const Error = () => {
4 | return
There was an error... Please try again!
5 | }
6 |
7 | export default Error
8 |
--------------------------------------------------------------------------------
/src/pages/Checkout.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Stripe_Checkout } from "../components"
3 |
4 | const Checkout = () => {
5 | return (
6 | <>
7 |
10 | >
11 | )
12 | }
13 |
14 | export default Checkout
15 |
--------------------------------------------------------------------------------
/src/components/Loading.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { CgSpinner } from "react-icons/cg"
3 | const Loading = () => {
4 | return (
5 |
6 | {" "}
7 | {" "}
8 |
9 | )
10 | }
11 |
12 | export default Loading
13 |
--------------------------------------------------------------------------------
/src/pages/News.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Breadcrumb } from "../components"
3 |
4 | const News = () => {
5 | return (
6 |
7 |
8 |
9 | Coming Soon...
10 |
11 |
12 | )
13 | }
14 |
15 | export default News
16 |
--------------------------------------------------------------------------------
/src/pages/Features.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Breadcrumb } from "../components"
3 |
4 | const Features = () => {
5 | return (
6 |
7 |
8 |
9 | Coming Soon...
10 |
11 |
12 | )
13 | }
14 |
15 | export default Features
16 |
--------------------------------------------------------------------------------
/src/pages/Services.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Breadcrumb } from "../components"
3 |
4 | const Services = () => {
5 | return (
6 |
7 |
8 |
9 | Coming Soon...
10 |
11 |
12 | )
13 | }
14 |
15 | export default Services
16 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | functions = "./functions"
3 |
4 | [[redirects]]
5 | from = "/*"
6 | to = "/index.html"
7 | status = 200
8 |
9 | [[headers]]
10 | for = "/manifest.webmanifest"
11 | [headers.values]
12 | Content-Type = "application/manifest+json"
13 |
14 | [[headers]]
15 | for = "/assets/*"
16 | [headers.values]
17 | cache-control = '''
18 | max-age=31536000,
19 | immutable
20 | '''
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | .env
11 | node_modules
12 | dist
13 | dist-ssr
14 | *.local
15 |
16 | # Editor directories and files
17 | .vscode/*
18 | !.vscode/extensions.json
19 | .idea
20 | .DS_Store
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 |
27 | # Local Netlify folder
28 | .netlify
29 |
--------------------------------------------------------------------------------
/src/pages/ProtectedRoute.jsx:
--------------------------------------------------------------------------------
1 | import { useAuth0 } from "@auth0/auth0-react"
2 | import React from "react"
3 | import { Navigate } from "react-router-dom"
4 |
5 | const ProtectedRoute = ({ children }) => {
6 | const { user, isLoading } = useAuth0()
7 | if (isLoading) return Loading...
8 | if (!user) {
9 | return
10 | }
11 | return children
12 | }
13 |
14 | export default ProtectedRoute
15 |
--------------------------------------------------------------------------------
/src/components/ProductsHome.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FiltersComponent from './Filters'
3 | import AllProducts from './AllProducts'
4 |
5 | const ProductsHome = () => {
6 | return (
7 | <>
8 |
12 | >
13 | )
14 | }
15 |
16 | export default ProductsHome
--------------------------------------------------------------------------------
/src/utils/helper.jsx:
--------------------------------------------------------------------------------
1 | export const formatPrice = (number) => {
2 | const newNumber = Intl.NumberFormat("en-US", {
3 | style: "currency",
4 | currency: "INR",
5 | }).format(number)
6 | return newNumber
7 | }
8 |
9 | export const getUniqueValues = (data, type) => {
10 | let dataMap = data.map((item) => item[type])
11 | if (type === "colors") {
12 | dataMap = dataMap.flat()
13 | }
14 | return ["all", ...new Set(dataMap)]
15 | }
--------------------------------------------------------------------------------
/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
4 | theme: {
5 | screens: {
6 | sm: "480px",
7 | md: "768px",
8 | lg: "976px",
9 | xl: "1440px",
10 | },
11 | extend: {
12 | colors: {
13 | primary: "#48c4a0",
14 | },
15 | },
16 | },
17 | plugins: [require("@tailwindcss/forms")],
18 | }
--------------------------------------------------------------------------------
/src/pages/Cart.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Breadcrumb, CartItems } from "../components"
3 | import { useCartContext } from "../context/cart/cart_context"
4 | import { Empty_cart } from "../components"
5 |
6 | const Cart = () => {
7 | const { cart } = useCartContext()
8 | if (cart.length === 0) return
9 | return (
10 | <>
11 |
12 |
13 | >
14 | )
15 | }
16 |
17 | export default Cart
18 |
--------------------------------------------------------------------------------
/src/components/Logo.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Link } from "react-router-dom"
3 |
4 | const Logo = ({ className }) => {
5 | return (
6 |
7 |
10 | Shoptik
11 |
12 |
13 |
14 | )
15 | }
16 |
17 | export default Logo
18 |
--------------------------------------------------------------------------------
/src/components/View_button.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Link } from "react-router-dom"
3 |
4 | const View_button = ({ title, className }) => {
5 | return (
6 | <>
7 |
8 |
11 | {title}
12 |
13 |
14 | >
15 | )
16 | }
17 |
18 | export default View_button
19 |
--------------------------------------------------------------------------------
/src/components/Breadcrumb.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Link } from "react-router-dom"
3 |
4 | const Breadcrumb = ({ title, product }) => {
5 | return (
6 | <>
7 |
8 |
9 | Home
10 | {product && / Products } / {title}
11 |
12 |
13 | >
14 | )
15 | }
16 |
17 | export default Breadcrumb
18 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;500;700;900&display=swap');
2 |
3 | @layer base {
4 | body {
5 | font-family: 'Roboto', sans-serif;
6 | letter-spacing: .05em
7 | }
8 | input[type="number"]::-webkit-inner-spin-button,
9 | input[type="number"]::-webkit-outer-spin-button {
10 | -webkit-appearance: none;
11 | margin: 0;
12 | }
13 | .scrollbar-hide::-webkit-scrollbar {
14 | display: none;
15 | }
16 | }
17 |
18 |
19 | @tailwind base;
20 | @tailwind components;
21 | @tailwind utilities;
--------------------------------------------------------------------------------
/src/utils/constants.jsx:
--------------------------------------------------------------------------------
1 | export const navLinks = [
2 | {
3 | id: 1,
4 | title: "home",
5 | url: "/",
6 | },
7 | {
8 | id: 2,
9 | title: "shop",
10 | url: "/shop",
11 | },
12 | {
13 | id: 3,
14 | title: "features",
15 | url: "/features",
16 | },
17 | {
18 | id: 4,
19 | title: "services",
20 | url: "/services",
21 | },
22 | {
23 | id: 5,
24 | title: "news",
25 | url: "/news",
26 | },
27 | ]
28 |
29 | export const products_url = ".netlify/functions/products"
30 | export const single_product_url = `/.netlify/functions/single-product?id=`
31 |
--------------------------------------------------------------------------------
/src/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import Cart from "./Cart"
2 | import Checkout from "./Checkout"
3 | import Completion from "./Completion"
4 | import Error from "./Error"
5 | import Features from "./Features"
6 | import Home from "./Home"
7 | import News from "./News"
8 | import Products from "./Products"
9 | import ProtectedRoute from "./ProtectedRoute"
10 | import Services from "./Services"
11 | import SingleProduct from "./SingleProduct"
12 |
13 | export {
14 | Features,
15 | Services,
16 | News,
17 | Completion,
18 | Cart,
19 | Checkout,
20 | Error,
21 | Home,
22 | Products,
23 | SingleProduct,
24 | ProtectedRoute,
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/Product_title.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Link } from "react-router-dom"
3 | import { formatPrice } from "../utils/helper"
4 |
5 | const Product_title = ({ product, className }) => {
6 | const { name, price, id } = product
7 | return (
8 | <>
9 |
10 |
14 | {" "}
15 | {name}
16 |
17 |
{formatPrice(price)}
18 |
19 | >
20 | )
21 | }
22 |
23 | export default Product_title
24 |
--------------------------------------------------------------------------------
/src/components/ProductImage.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Link } from "react-router-dom"
3 |
4 | const ProductImage = ({ product, className }) => {
5 | const { image, name, id } = product
6 | return (
7 | <>
8 |
9 |
10 |

17 |
18 |
19 | >
20 | )
21 | }
22 |
23 | export default ProductImage
24 |
--------------------------------------------------------------------------------
/src/components/AllProducts.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { useProductsContext } from "../context/product/products_context"
3 | import {
4 | Loading,
5 | Error,
6 | Grid_view_products,
7 | List_view_products,
8 | } from "../components"
9 | import { useFilterContext } from "../context/filter/filter_context"
10 |
11 | const AllProducts = () => {
12 | const { products_loading, products_error } = useProductsContext()
13 | const { filtered_products: products, grid_view } = useFilterContext()
14 |
15 | if (products_loading) return
16 | if (products_error) return
17 | if (grid_view === false) return
18 |
19 | return (
20 | <>
21 |
22 | >
23 | )
24 | }
25 |
26 | export default AllProducts
27 |
--------------------------------------------------------------------------------
/src/components/Grid_view_products.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { ProductImage, Product_title } from "../components"
3 | const Grid_view_products = ({ products }) => {
4 | return (
5 | <>
6 |
7 | {products.map((product) => {
8 | const { id } = product
9 | return (
10 |
11 |
15 |
19 |
20 | )
21 | })}
22 |
23 | >
24 | )
25 | }
26 |
27 | export default Grid_view_products
28 |
--------------------------------------------------------------------------------
/src/components/ProductGrid.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { CollectionProducts } from "../components"
3 | import { useProductsContext } from "../context/product/products_context"
4 |
5 | const ProductGrid = () => {
6 | const { featured_products, newArrival_products, bestSeller_products } =
7 | useProductsContext()
8 |
9 | return (
10 |
11 | {/* Bestseller */}
12 |
13 |
14 | {/* New Arrival */}
15 |
16 |
17 | {/* Featured Products */}
18 |
19 |
20 | )
21 | }
22 |
23 | export default ProductGrid
24 |
--------------------------------------------------------------------------------
/src/pages/Error.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Link } from "react-router-dom"
3 | import PageNotFound from "../assets/undraw_page_not_found.svg"
4 |
5 | const Error = () => {
6 | return (
7 | <>
8 |
9 |
10 |
11 |
Page Not Found
12 |
13 | The page you're looking for does not seem to exist.
14 |
15 |
19 | {" "}
20 | Go to home{" "}
21 |
22 |
23 |
24 | >
25 | )
26 | }
27 |
28 | export default Error
29 |
--------------------------------------------------------------------------------
/src/components/CollectionProducts.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { ProductImage, Product_title } from "../components"
3 |
4 | const CollectionProducts = ({ products, title }) => {
5 | return (
6 | <>
7 |
8 |
9 | {" "}
10 | {title}{" "}
11 |
12 |
13 | {products.slice(0, 3).map((product) => {
14 | const { id } = product
15 | return (
16 |
23 | )
24 | })}
25 |
26 | >
27 | )
28 | }
29 |
30 | export default CollectionProducts
31 |
--------------------------------------------------------------------------------
/src/components/Empty_cart.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import Empty_Cart_Image from "../assets/undraw_empty_cart.svg"
3 | import { Link } from "react-router-dom"
4 |
5 | const Empty_cart = () => {
6 | return (
7 |
8 |
9 |
10 |
Your cart is empty.
11 |
12 | Looks like you have not added anything to your cart.
13 |
14 |
18 | {" "}
19 | Add items{" "}
20 |
21 |
22 |
23 | )
24 | }
25 |
26 | export default Empty_cart
27 |
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import { Auth0Provider } from "@auth0/auth0-react"
2 | import React from "react"
3 | import ReactDOM from "react-dom/client"
4 | import App from "./App"
5 | import { CartProvider } from "./context/cart/cart_context"
6 | import { FiltersProvider } from "./context/filter/filter_context"
7 | import { ProductsProvider } from "./context/product/products_context"
8 | import "./index.css"
9 | ReactDOM.createRoot(document.getElementById("root")).render(
10 |
11 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | )
29 |
--------------------------------------------------------------------------------
/functions/single-product.js:
--------------------------------------------------------------------------------
1 | import dotenv from "dotenv"
2 | dotenv.config()
3 |
4 | import Airtable from "airtable-node"
5 |
6 |
7 | const airtable = new Airtable({ apiKey: process.env.AIRTABLE_API_KEY })
8 | .base(process.env.AIRTABLE_BASE)
9 | .table(process.env.AIRTABLE_TABLE)
10 |
11 | exports.handler = async (event, context, cb) => {
12 | const { id } = event.queryStringParameters
13 | if (id) {
14 | try {
15 | let product = await airtable.retrieve(id)
16 | if (product.error) {
17 | return {
18 | statusCode: 404,
19 | body: `No product with id: ${id}`,
20 | }
21 | }
22 | product = { id: product.id, ...product.fields }
23 | return {
24 | statusCode: 200,
25 | body: JSON.stringify(product),
26 | }
27 | } catch (error) {
28 | return {
29 | statusCode: 500,
30 | body: `Server Error`,
31 | }
32 | }
33 | }
34 | return {
35 | statusCode: 400,
36 | body: "Please provide product id",
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/List_view_products.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { ProductImage, Product_title } from "../components"
3 |
4 | const List_view_products = ({ products }) => {
5 | return (
6 | <>
7 |
8 | {products.map((product) => {
9 | const { id } = product
10 | return (
11 |
15 |
19 |
22 |
23 | )
24 | })}
25 |
26 | >
27 | )
28 | }
29 |
30 | export default List_view_products
31 |
--------------------------------------------------------------------------------
/src/components/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import NotFoundProduct from "../assets/undraw_search_engine.svg"
3 | import { useFilterContext } from "../context/filter/filter_context"
4 | const NotFound = () => {
5 | const {
6 | filters: { text },
7 | } = useFilterContext()
8 | return (
9 |
10 |
11 | {" "}
12 | You searched for
13 | {text} {" "}
14 |
15 |
16 |
17 |
18 | {" "}
19 | We could't find any matches!{" "}
20 |
21 |
22 | Please check the spelling or try searching something else
23 |
24 |
25 |
26 | )
27 | }
28 |
29 | export default NotFound
30 |
--------------------------------------------------------------------------------
/src/components/ProductCategory.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { useProductsContext } from "../context/product/products_context"
3 | import { CategoryProducts } from "../components"
4 | const ProductCategory = () => {
5 | const { products } = useProductsContext()
6 |
7 | const all_Decoration_products = products.filter(
8 | (product) => product.category === "decoration"
9 | )
10 | const all_Architect_products = products.filter(
11 | (product) => product.category === "architect"
12 | )
13 |
14 | return (
15 | <>
16 |
28 | >
29 | )
30 | }
31 |
32 | export default ProductCategory
33 |
--------------------------------------------------------------------------------
/src/pages/Home.jsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense, lazy } from "react"
2 |
3 | import { useProductsContext } from "../context/product/products_context"
4 | import { Loading } from "../components"
5 |
6 | const HomeProduct = lazy(() => import("../components/HomeProduct"))
7 | const BasketProduct = lazy(() => import("../components/Basket"))
8 | const Header = lazy(() => import("../components/Header"))
9 | const Instagram = lazy(() => import("../components/Instagram"))
10 | const ProductCategory = lazy(() => import("../components/ProductCategory"))
11 | const ProductGrid = lazy(() => import("../components/ProductGrid"))
12 |
13 | const Home = () => {
14 | const { products_loading: loading } = useProductsContext()
15 | if (loading) return
16 | return (
17 | <>
18 | }>
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | >
27 | )
28 | }
29 |
30 | export default Home
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Kumar Avishek (Saurav)
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.
--------------------------------------------------------------------------------
/src/components/Stars.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { BsStarFill, BsStarHalf, BsStar } from "react-icons/bs"
3 |
4 | const Stars = ({ stars, reviews }) => {
5 | const tempStars = Array.from({ length: 5 }, (_, index) => {
6 | const number = index + 0.5
7 | return (
8 |
9 | {stars > number ? (
10 |
11 | ) : stars > index ? (
12 |
13 | ) : (
14 |
15 | )}
16 |
17 | )
18 | })
19 |
20 | return (
21 |
22 |
23 |
24 | {" "}
25 | {tempStars}
26 | {" "}
27 | {stars}
28 |
29 |
{reviews} Reviews
30 |
31 | )
32 | }
33 |
34 | export default Stars
35 |
--------------------------------------------------------------------------------
/src/components/Basket.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import BasketImage from "../assets/basketChair.webp"
3 | import { View_button } from "../components"
4 |
5 | const Basket = () => {
6 | return (
7 |
8 |
9 | {/* Mobile image */}
10 |

17 |
18 | {" "}
19 | Stylish minimal chair
20 |
21 |
22 |
23 | {/* XL Image */}
24 |
29 |
30 | )
31 | }
32 |
33 | export default Basket
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Shoptik-ecommerce-web-app",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite ",
8 | "start": "netlify dev --targetPort 5173 ",
9 | "netlify": "netlify dev",
10 | "build": "vite build",
11 | "preview": "vite preview"
12 | },
13 | "dependencies": {
14 | "@auth0/auth0-react": "^2.0.0",
15 | "@stripe/react-stripe-js": "^1.16.4",
16 | "@stripe/stripe-js": "^1.46.0",
17 | "airtable-node": "^0.1.20",
18 | "axios": "^1.2.5",
19 | "dotenv": "^16.0.3",
20 | "react": "^18.2.0",
21 | "react-dom": "^18.2.0",
22 | "react-ga4": "^2.1.0",
23 | "react-icons": "^4.4.0",
24 | "react-router-dom": "^6.4.2",
25 | "stripe": "^11.11.0"
26 | },
27 | "devDependencies": {
28 | "@tailwindcss/forms": "^0.5.3",
29 | "@types/react": "^18.0.17",
30 | "@types/react-dom": "^18.0.6",
31 | "@vitejs/plugin-react": "^2.1.0",
32 | "autoprefixer": "^10.4.12",
33 | "netlify-cli": "^12.9.2",
34 | "postcss": "^8.4.17",
35 | "prettier": "^2.8.4",
36 | "prettier-plugin-tailwindcss": "^0.2.4",
37 | "tailwindcss": "^3.1.8",
38 | "vite": "^3.1.0",
39 | "vite-plugin-pwa": "^0.15.0",
40 | "workbox-build": "^6.5.4",
41 | "workbox-window": "^6.5.4"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/pages/Products.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react"
2 | import {
3 | Breadcrumb,
4 | Filters,
5 | AllProducts,
6 | Sort,
7 | NotFound,
8 | Sidebar_Filter,
9 | Loading,
10 | } from "../components"
11 | import { useFilterContext } from "../context/filter/filter_context"
12 | import { useProductsContext } from "../context/product/products_context"
13 | import ReactGA from "react-ga4"
14 | const Products = () => {
15 | const { filtered_products: products } = useFilterContext()
16 | const { products_loading } = useProductsContext()
17 | if (products_loading) return
18 |
19 | useEffect(() => {
20 | ReactGA.send({
21 | hitType: "pageView",
22 | page: window.location.pathname,
23 | title: "Shop page",
24 | })
25 | })
26 | return (
27 | <>
28 |
29 |
30 |
31 |
32 | {products.length < 1 &&
}
33 | {products.length >= 1 &&
}
34 |
35 |
36 | {/* Sidebar filter */}
37 |
38 |
39 | >
40 | )
41 | }
42 |
43 | export default Products
44 |
--------------------------------------------------------------------------------
/src/actions/actions.jsx:
--------------------------------------------------------------------------------
1 | // Sidebar actions
2 | export const SIDEBAR_OPEN = "SIDEBAR_OPEN"
3 | export const SIDEBAR_CLOSE = "SIDEBAR_CLOSE"
4 |
5 | // All products actions
6 | export const GET_PRODUCTS_BEGIN = "GET_PRODUCTS_BEGIN"
7 | export const GET_PRODUCTS_SUCCESS = "GET_PRODUCTS_SUCCESS"
8 | export const GET_PRODUCTS_ERROR = "GET_PRODUCTS_ERROR"
9 |
10 | // Single products actions
11 | export const GET_SINGLE_PRODUCT_BEGIN = "GET_SINGLE_PRODUCT_BEGIN"
12 | export const GET_SINGLE_PRODUCT_SUCCESS = "GET_SINGLE_PRODUCT_SUCCESS"
13 | export const GET_SINGLE_PRODUCT_ERROR = "GET_SINGLE_PRODUCT_ERROR"
14 |
15 | export const LOAD_PRODUCTS = "LOAD_PRODUCTS"
16 |
17 | // View actions
18 | export const SET_GRID_VIEW = "SET_GRID_VIEW"
19 | export const SET_LIST_VIEW = "SET_LIST_VIEW"
20 |
21 | // Filter actions
22 | export const UPDATE_SORT = "UPDATE_SORT"
23 | export const SORT_PRODUCTS = "SORT_PRODUCTS"
24 | export const UPDATE_FILTERS = "UPDATE_FILTERS"
25 | export const FILTER_PRODUCTS = "FILTER_PRODUCTS"
26 | export const CLEAR_FILTERS = "CLEAR_FILTERS"
27 |
28 | // Cart actions
29 | export const ADD_TO_CART = "ADD_TO_CART"
30 | export const REMOVE_CART_ITEM = "REMOVE_CART_ITEM"
31 | export const TOGGLE_CART_ITEM_AMOUNT = "TOGGLE_CART_ITEM_AMOUNT"
32 | export const CLEAR_CART = "CLEAR_CART"
33 | export const COUNT_CART_TOTALS = "COUNT_CART_TOTALS"
34 |
--------------------------------------------------------------------------------
/functions/stripe-payment-intent.js:
--------------------------------------------------------------------------------
1 | import dotenv from "dotenv"
2 | dotenv.config()
3 | // Connect to the Stripe payment system
4 | import Stripe from "stripe"
5 | const stripe = new Stripe(process.env.VITE_STRIPE_SECRET_KEY)
6 |
7 | exports.handler = async (event, context) => {
8 | const { shipping_fee, total_amount } = JSON.parse(event.body)
9 | const calculateOrderAmount = () => {
10 | // Calculate the order total on the server to prevent
11 | // people from directly manipulating the amount on the client
12 | return (shipping_fee + total_amount) * 100
13 | }
14 | try {
15 | // Create a PaymentIntent with the order amount and currency
16 | const paymentIntent = await stripe.paymentIntents.create({
17 | amount: calculateOrderAmount(),
18 | currency: "INR",
19 | description: "product name",
20 | shipping: {
21 | name: "user name",
22 | address: {
23 | line1: "510 Townsend St",
24 | postal_code: "98140",
25 | city: "San Francisco",
26 | state: "CA",
27 | country: "US",
28 | },
29 | },
30 | })
31 |
32 | return {
33 | statusCode: 200,
34 | body: JSON.stringify({ clientSecret: paymentIntent.client_secret }),
35 | }
36 | } catch (error) {
37 | return {
38 | statusCode: 500,
39 | body: JSON.stringify({ error: error.message }),
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Light1 from "../assets/light1.webp"
3 | import Light2 from "../assets/light2.webp"
4 | import { View_button } from "../components"
5 | const Header = () => {
6 | return (
7 |
8 |
9 |
10 |
11 | {" "}
12 | Shoptik Number #1Trusted
13 | furniture website.{" "}
14 |
15 |
16 | coming soon in your door with a huge discount.
17 |
18 |
19 |
20 |
21 |
26 |
31 |
32 | )
33 | }
34 |
35 | export default Header
--------------------------------------------------------------------------------
/src/components/Stripe_Checkout.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react"
2 | import { loadStripe } from "@stripe/stripe-js"
3 | import { useCartContext } from "../context/cart/cart_context"
4 | import CardStyle from "../app.css"
5 | import axios from "axios"
6 | import { Elements } from "@stripe/react-stripe-js"
7 | import CheckoutForm from "./CheckoutForm"
8 |
9 | const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY)
10 |
11 | const Stripe_Checkout = () => {
12 | const { cart, total_amount, shipping_fee } = useCartContext()
13 | // Stripe Stuff
14 | const [clientSecret, setClientSecret] = useState("")
15 |
16 | const createPaymentIntent = async () => {
17 | try {
18 | const { data } = await axios.post(
19 | "/.netlify/functions/stripe-payment-intent",
20 | JSON.stringify({ cart, shipping_fee, total_amount })
21 | )
22 | setClientSecret(data.clientSecret)
23 | } catch (error) {
24 | // console.log(error.response)
25 | }
26 | }
27 |
28 | const appearance = {
29 | theme: "stripe",
30 | }
31 | const options = {
32 | clientSecret,
33 | appearance,
34 | }
35 |
36 | useEffect(() => {
37 | createPaymentIntent()
38 | }, [])
39 |
40 | return (
41 |
42 | {clientSecret && (
43 |
44 |
45 |
46 | )}
47 |
48 | )
49 | }
50 |
51 | export default Stripe_Checkout
52 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { Navbar, Footer } from "./components"
2 | import { BrowserRouter, Route, Routes } from "react-router-dom"
3 | import {
4 | Home,
5 | Cart,
6 | Products,
7 | SingleProduct,
8 | Error,
9 | Checkout,
10 | ProtectedRoute,
11 | Completion,
12 | Features,
13 | Services,
14 | News,
15 | } from "./pages"
16 | import ReactGA from "react-ga4"
17 | const measurementID = "G-Y1EV1Q38PH"
18 | ReactGA.initialize(measurementID)
19 | function App() {
20 | return (
21 |
22 |
23 |
24 | } />
25 | } />
26 | } />
27 | } />
28 |
32 | {" "}
33 | {" "}
34 |
35 | }
36 | />
37 |
41 | {" "}
42 | {" "}
43 |
44 | }
45 | />
46 |
47 | } />
48 | } />
49 | } />
50 | } />
51 |
52 |
53 |
54 | )
55 | }
56 |
57 | export default App
58 |
--------------------------------------------------------------------------------
/dev-dist/sw.js:
--------------------------------------------------------------------------------
1 | if(!self.define){let e,s={};const t=(t,n)=>(t=new URL(t+".js",n).href,s[t]||new Promise((s=>{if("document"in self){const e=document.createElement("script");e.src=t,e.onload=s,document.head.appendChild(e)}else e=t,importScripts(t),s()})).then((()=>{let e=s[t];if(!e)throw new Error(`Module ${t} didn’t register its module`);return e})));self.define=(n,i)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(s[o])return;let c={};const r=e=>t(e,o),l={module:{uri:o},exports:c,require:r};s[o]=Promise.all(n.map((e=>l[e]||r(e)))).then((e=>(i(...e),c)))}}define(["./workbox-558f421c"],(function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"3ca0b8505b4bec776b69afdba2768812"},{revision:null,url:"index.html"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html"),{allowlist:[/^\/$/]})),e.registerRoute(/^https:\/\/fonts\.googleapis\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-cache",plugins:[new e.ExpirationPlugin({maxEntries:10,maxAgeSeconds:31536e3}),new e.CacheableResponsePlugin({statuses:[0,200]})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.gstatic\.com\/.*/i,new e.CacheFirst({cacheName:"gstatic-fonts-cache",plugins:[new e.ExpirationPlugin({maxEntries:10,maxAgeSeconds:31536e3}),new e.CacheableResponsePlugin({statuses:[0,200]})]}),"GET"),e.registerRoute((({url:e})=>e.pathname.startsWith("/.netlify")),new e.StaleWhileRevalidate({cacheName:"api-cache",plugins:[new e.ExpirationPlugin({maxEntries:10,maxAgeSeconds:7200}),new e.CacheableResponsePlugin({statuses:[0,200]})]}),"GET")}));
2 |
--------------------------------------------------------------------------------
/src/components/Instagram.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Instagram1 from "../assets/instaGram1.webp"
3 | import Instagram2 from "../assets/instaGram2.webp"
4 | import Instagram3 from "../assets/instaGram3.webp"
5 | import Instagram4 from "../assets/instaGram4.webp"
6 | import Blog3 from "../assets/Blog3.webp"
7 |
8 | const Instagram = () => {
9 | return (
10 |
44 | )
45 | }
46 |
47 | export default Instagram
--------------------------------------------------------------------------------
/src/components/ProductImages.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { useState } from "react"
3 |
4 | const ProductImages = ({ product, images = [{ url: "" }] }) => {
5 | const [main, setMain] = useState(images[0])
6 | return (
7 | <>
8 |
9 |
10 |

15 | {product.stock === 0 && (
16 |
17 | {" "}
18 |
Out of stock
{" "}
19 |
20 | )}
21 |
22 |
23 | {images.map((image, index) => {
24 | return (
25 |
26 |

setMain(images[index])}
30 | className={`h-20 w-20 border object-cover object-center p-4 mix-blend-multiply md:h-32 md:w-36 ${
31 | image.url === main.url ? "border-2 border-primary" : null
32 | } `}
33 | />
34 |
35 | )
36 | })}
37 |
38 |
39 | >
40 | )
41 | }
42 |
43 | export default ProductImages
44 |
--------------------------------------------------------------------------------
/src/components/Cart_Button.jsx:
--------------------------------------------------------------------------------
1 | import { useAuth0 } from "@auth0/auth0-react"
2 | import { BsCart2, BsPersonPlus, BsPersonDash } from "react-icons/bs"
3 | import { Link } from "react-router-dom"
4 | import { useCartContext } from "../context/cart/cart_context"
5 |
6 | const Cart_Button = () => {
7 | const { user, loginWithRedirect, logout } = useAuth0()
8 | const { total_items, clearCart } = useCartContext()
9 | return (
10 | <>
11 |
12 |
17 | {" "}
18 |
{" "}
19 |
20 | {" "}
21 | {total_items}
22 |
23 |
24 |
25 | {user ? (
26 |
36 | ) : (
37 |
44 | )}
45 |
46 |
47 | >
48 | )
49 | }
50 |
51 | export default Cart_Button
52 |
--------------------------------------------------------------------------------
/src/components/CategoryProducts.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { ProductImage, Product_title } from "../components"
3 | import { View_button } from "../components"
4 |
5 | const CategoryProducts = ({ products, category_name }) => {
6 | return (
7 | <>
8 |
9 |
10 |
11 |
12 | {category_name}
13 |
14 |
15 | Small be and the rain would phase distance, succeed align.
16 |
17 |
18 |
19 |
20 | {products.slice(0, 3).map((product) => {
21 | const { id, new_in_market, sale } = product
22 | return (
23 |
27 |
28 |
29 | {sale && (
30 |
33 | )}
34 | {new_in_market && (
35 |
38 | )}
39 |
40 | )
41 | })}
42 |
43 | >
44 | )
45 | }
46 |
47 | export default CategoryProducts
48 |
--------------------------------------------------------------------------------
/src/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Link } from "react-router-dom"
3 | import { navLinks } from "../utils/constants"
4 | import { BsList } from "react-icons/bs"
5 | import { Sidebar, Logo, Cart_Button } from "../components"
6 | import { useProductsContext } from "../context/product/products_context"
7 |
8 | const Navbar = () => {
9 | const { openSidebar, isSidebarOpen } = useProductsContext()
10 | return (
11 | <>
12 |
47 | {isSidebarOpen ? : null}
48 | >
49 | )
50 | }
51 |
52 | export default Navbar
53 |
--------------------------------------------------------------------------------
/functions/products.js:
--------------------------------------------------------------------------------
1 | import dotenv from "dotenv"
2 | dotenv.config()
3 |
4 | import Airtable from "airtable-node"
5 |
6 | const airtable = new Airtable({ apiKey: process.env.AIRTABLE_API_KEY })
7 | .base(process.env.AIRTABLE_BASE)
8 | .table(process.env.AIRTABLE_TABLE)
9 |
10 |
11 | exports.handler = async () => {
12 | try {
13 | const response = await airtable.list({ maxRecords: 100 })
14 |
15 | const products = response.records.map((product) => {
16 | const { id, fields } = product
17 | const {
18 | weight,
19 | price,
20 | most_popular,
21 | bestseller,
22 | material,
23 | stock,
24 | new_arrival,
25 | designer,
26 | company,
27 | hot_collection,
28 | colors,
29 | category,
30 | description,
31 | reviews,
32 | stars,
33 | name,
34 | notes,
35 | featured,
36 | sale,
37 | trending,
38 | shipping,
39 | origin,
40 | images,
41 | exclusive,
42 | new_in_market,
43 | } = fields
44 | const { url } = images[0]
45 | return {
46 | id,
47 | weight,
48 | price,
49 | most_popular,
50 | bestseller,
51 | material,
52 | stock,
53 | new_arrival,
54 | designer,
55 | company,
56 | hot_collection,
57 | colors,
58 | category,
59 | description,
60 | reviews,
61 | stars,
62 | name,
63 | notes,
64 | featured,
65 | sale,
66 | trending,
67 | shipping,
68 | origin,
69 | image: url,
70 | images,
71 | exclusive,
72 | new_in_market,
73 | }
74 | })
75 | return {
76 | statusCode: 200,
77 | body: JSON.stringify(products),
78 | }
79 | } catch (error) {
80 | console.log(error)
81 | return {
82 | statusCode: 500,
83 | body: "There was an error",
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/components/Sidebar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react"
2 | import { Link } from "react-router-dom"
3 | import { navLinks } from "../utils/constants"
4 | import { BsX } from "react-icons/bs"
5 | import { Logo } from "../components"
6 | import { useProductsContext } from "../context/product/products_context"
7 |
8 | const Sidebar = () => {
9 | const { closeSidebar } = useProductsContext()
10 | const sidebarRef = useRef(null)
11 |
12 | useEffect(() => {
13 | const handleClickOutside = (e) => {
14 | if (sidebarRef.current && !sidebarRef.current.contains(e.target)) {
15 | closeSidebar()
16 | }
17 | }
18 | document.addEventListener("mousedown", handleClickOutside)
19 | return () => {
20 | document.removeEventListener("mousedown", handleClickOutside)
21 | }
22 | })
23 |
24 | return (
25 |
58 | )
59 | }
60 |
61 | export default Sidebar
62 |
--------------------------------------------------------------------------------
/src/components/index.jsx:
--------------------------------------------------------------------------------
1 | import Navbar from './Navbar'
2 | import Footer from "./Footer"
3 | import Sidebar from "./Sidebar"
4 | import Logo from "./Logo"
5 | import Breadcrumb from "./Breadcrumb"
6 | import Header from "./Header"
7 | import HomeProduct from "./HomeProduct"
8 | import BasketProduct from "./Basket"
9 | import Instagram from "./Instagram"
10 | import ProductCategory from "./ProductCategory"
11 | import ProductsHome from "./ProductsHome"
12 | import ProductGrid from "./ProductGrid"
13 | import CollectionProducts from "./CollectionProducts"
14 | import CategoryProducts from "./CategoryProducts"
15 | import AllProducts from "./AllProducts"
16 | import Filters from "./Filters"
17 | import Loading from "./Loading"
18 | import Error from "./Error"
19 | import ProductImages from "./ProductImages"
20 | import Sort from "./Sort"
21 | import Grid_view_products from "./Grid_view_products"
22 | import List_view_products from "./List_view_products"
23 | import AddToCart from "./AddToCart"
24 | import ProductImage from "./ProductImage"
25 | import Product_title from "./Product_title"
26 | import View_button from "./View_button"
27 | import Order_summary from "./Order_summary"
28 | import Empty_cart from "./Empty_cart"
29 | import Stripe_Checkout from "./Stripe_Checkout"
30 | import Cart_Button from "./Cart_Button"
31 | import NotFound from "./NotFound"
32 | import CartItems from "./CartItems"
33 | import Stars from "./Stars"
34 | import Sidebar_Filter from "./Sidebar_Filter"
35 | export {
36 | Sidebar_Filter,
37 | Stars,
38 | CartItems,
39 | NotFound,
40 | Cart_Button,
41 | Stripe_Checkout,
42 | Empty_cart,
43 | Order_summary,
44 | View_button,
45 | Product_title,
46 | ProductImage,
47 | AddToCart,
48 | List_view_products,
49 | Grid_view_products,
50 | Sort,
51 | ProductImages,
52 | Error,
53 | Loading,
54 | Filters,
55 | AllProducts,
56 | CategoryProducts,
57 | CollectionProducts,
58 | ProductGrid,
59 | Instagram,
60 | ProductCategory,
61 | ProductsHome,
62 | BasketProduct,
63 | HomeProduct,
64 | Header,
65 | Breadcrumb,
66 | Navbar,
67 | Footer,
68 | Sidebar,
69 | Logo,
70 | }
--------------------------------------------------------------------------------
/src/components/Sort.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { BsFillGrid3X3GapFill } from "react-icons/bs"
3 | import { FaThList } from "react-icons/fa"
4 | import { useFilterContext } from "../context/filter/filter_context"
5 |
6 | const Sort = () => {
7 | const {
8 | filtered_products: products,
9 | grid_view,
10 | setGridView,
11 | setListView,
12 | sort,
13 | updateSort,
14 | } = useFilterContext()
15 | return (
16 | <>
17 | {/* Total products count and sort filter */}
18 |
19 |
20 |
28 |
36 |
37 | {" "}
38 | {products.length} Product{products.length > 1 && "s"} found{" "}
39 |
40 |
41 |
42 |
59 |
60 | >
61 | )
62 | }
63 |
64 | export default Sort
65 |
--------------------------------------------------------------------------------
/src/context/cart/cart_context.jsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useReducer } from "react"
2 | import reducer from "../../reducers/cart/cart_reducer"
3 | import {
4 | ADD_TO_CART,
5 | REMOVE_CART_ITEM,
6 | TOGGLE_CART_ITEM_AMOUNT,
7 | CLEAR_CART,
8 | COUNT_CART_TOTALS,
9 | } from "../../actions/actions"
10 |
11 | const getLocalStorage = () => {
12 | let cart = localStorage.getItem("cart")
13 | if (cart) {
14 | return JSON.parse(localStorage.getItem("cart"))
15 | } else {
16 | return []
17 | }
18 | }
19 |
20 | const initialState = {
21 | cart: getLocalStorage(),
22 | total_items: 0,
23 | total_amount: 0,
24 | shipping_fee: 99,
25 | }
26 |
27 | const CartContext = createContext()
28 | export const CartProvider = ({ children }) => {
29 | const [state, dispatch] = useReducer(reducer, initialState)
30 |
31 | // Add to Cart
32 | const addToCart = (id, color, amount, product) => {
33 | dispatch({ type: ADD_TO_CART, payload: { id, color, amount, product } })
34 | window.dataLayer = window.dataLayer || []
35 | window.dataLayer.push({
36 | event: "add_to_cart",
37 | ecommerce: {
38 | currency: "INR",
39 | value: amount * product.price,
40 | items: [
41 | {
42 | item_name: product.name,
43 | item_id: product.id,
44 | price: product.price * amount,
45 | quantity: amount,
46 | },
47 | ],
48 | },
49 | })
50 | }
51 |
52 | // Remove item
53 | const removeItem = (id) => {
54 | dispatch({ type: REMOVE_CART_ITEM, payload: { id } })
55 | }
56 |
57 | // Toggle amount
58 | const toggleAmount = (id, value) => {
59 | dispatch({ type: TOGGLE_CART_ITEM_AMOUNT, payload: { id, value } })
60 | }
61 |
62 | // Clear cart
63 | const clearCart = () => {
64 | dispatch({ type: CLEAR_CART })
65 | }
66 |
67 | useEffect(() => {
68 | dispatch({ type: COUNT_CART_TOTALS })
69 | localStorage.setItem("cart", JSON.stringify(state.cart))
70 | }, [state.cart])
71 |
72 | return (
73 |
76 | {children}
77 |
78 | )
79 | }
80 |
81 | export const useCartContext = () => {
82 | return useContext(CartContext)
83 | }
84 |
--------------------------------------------------------------------------------
/src/reducers/product/products_reducer.jsx:
--------------------------------------------------------------------------------
1 | // Import actions variable
2 | import {
3 | SIDEBAR_OPEN,
4 | SIDEBAR_CLOSE,
5 | GET_PRODUCTS_BEGIN,
6 | GET_PRODUCTS_SUCCESS,
7 | GET_PRODUCTS_ERROR,
8 | GET_SINGLE_PRODUCT_BEGIN,
9 | GET_SINGLE_PRODUCT_SUCCESS,
10 | GET_SINGLE_PRODUCT_ERROR,
11 | } from "../../actions/actions"
12 |
13 | const products_reducer = (state, action) => {
14 | if (action.type === SIDEBAR_OPEN) {
15 | return { ...state, isSidebarOpen: true }
16 | }
17 | if (action.type === SIDEBAR_CLOSE) {
18 | return { ...state, isSidebarOpen: false }
19 | }
20 | //* ================= All Products actions =================
21 | if (action.type === GET_PRODUCTS_BEGIN) {
22 | return { ...state, products_loading: true }
23 | }
24 | if (action.type === GET_PRODUCTS_SUCCESS) {
25 | const allFeaturedProducts = action.payload.filter(
26 | (product) => product.featured === true
27 | )
28 | const allNewArrivalProducts = action.payload.filter(
29 | (product) => product.new_arrival === true
30 | )
31 | const allBestSeller_products = action.payload.filter(
32 | (product) => product.bestseller === true
33 | )
34 | return {
35 | ...state,
36 | products_loading: false,
37 | products: action.payload,
38 | featured_products: allFeaturedProducts,
39 | newArrival_products: allNewArrivalProducts,
40 | bestSeller_products: allBestSeller_products,
41 | }
42 | }
43 | if (action.type === GET_PRODUCTS_ERROR) {
44 | return { ...state, products_loading: false, products_error: true }
45 | }
46 | //* ================= END =================
47 |
48 | //* ================= Single Product actions =================
49 |
50 | if (action.type === GET_SINGLE_PRODUCT_BEGIN) {
51 | return {
52 | ...state,
53 | single_product_error: false,
54 | single_product_loading: true,
55 | }
56 | }
57 | if (action.type === GET_SINGLE_PRODUCT_SUCCESS) {
58 | return {
59 | ...state,
60 | single_product_loading: false,
61 | singleProduct: action.payload,
62 | }
63 | }
64 | if (action.payload === GET_SINGLE_PRODUCT_ERROR) {
65 | return {
66 | ...state,
67 | single_product_loading: false,
68 | single_product_error: true,
69 | }
70 | }
71 | //* ================= END =================
72 |
73 | throw new Error(`No matching "${action.type}" - action type `)
74 | }
75 |
76 | export default products_reducer
77 |
--------------------------------------------------------------------------------
/src/context/product/products_context.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react"
2 | import { useReducer } from "react"
3 | import reducer from "../../reducers/product/products_reducer"
4 | import { products_url as url, single_product_url } from "../../utils/constants"
5 |
6 | // Import actions variable
7 | import {
8 | SIDEBAR_OPEN,
9 | SIDEBAR_CLOSE,
10 | GET_PRODUCTS_BEGIN,
11 | GET_PRODUCTS_SUCCESS,
12 | GET_PRODUCTS_ERROR,
13 | GET_SINGLE_PRODUCT_BEGIN,
14 | GET_SINGLE_PRODUCT_ERROR,
15 | GET_SINGLE_PRODUCT_SUCCESS,
16 | } from "../../actions/actions"
17 | import axios from "axios"
18 | import { useEffect } from "react"
19 |
20 | const ProductsContext = React.createContext()
21 |
22 | // Create initial state for userReducer function
23 | const initialState = {
24 | isSidebarOpen: false,
25 | products_loading: false,
26 | products_error: false,
27 | products: [],
28 | featured_products: [],
29 | newArrival_products: [],
30 | bestSeller_products: [],
31 | single_product_loading: false,
32 | single_product_error: false,
33 | singleProduct: {},
34 | }
35 |
36 | export const ProductsProvider = ({ children }) => {
37 | // Create userReducer function
38 | const [state, dispatch] = useReducer(reducer, initialState)
39 |
40 | // Open sidebar
41 | const openSidebar = () => {
42 | dispatch({ type: SIDEBAR_OPEN })
43 | }
44 | // Close sidebar
45 | const closeSidebar = () => {
46 | dispatch({ type: SIDEBAR_CLOSE })
47 | }
48 |
49 | // Fetch all products
50 | const fetchAllProducts = async (url) => {
51 | dispatch({ type: GET_PRODUCTS_BEGIN })
52 | try {
53 | const response = await axios.get(url)
54 | const products = response.data
55 | dispatch({ type: GET_PRODUCTS_SUCCESS, payload: products })
56 | } catch (error) {
57 | dispatch({ type: GET_PRODUCTS_ERROR })
58 | }
59 | }
60 |
61 | // Fetch single product
62 | const fetchSingleProduct = async (url) => {
63 | dispatch({ type: GET_SINGLE_PRODUCT_BEGIN })
64 | try {
65 | const response = await axios.get(url)
66 | const single_product = response.data
67 | dispatch({ type: GET_SINGLE_PRODUCT_SUCCESS, payload: single_product })
68 | } catch (error) {
69 | dispatch({ type: GET_SINGLE_PRODUCT_ERROR })
70 | }
71 | }
72 |
73 | useEffect(() => {
74 | fetchAllProducts(url)
75 | window.scrollTo(0, 0)
76 | }, [])
77 |
78 | return (
79 |
82 | {children}
83 |
84 | )
85 | }
86 |
87 | // Create custom hook
88 | export const useProductsContext = () => {
89 | return useContext(ProductsContext)
90 | }
91 |
--------------------------------------------------------------------------------
/src/context/filter/filter_context.jsx:
--------------------------------------------------------------------------------
1 | import { useReducer } from "react"
2 | import { useContext } from "react"
3 | import { createContext } from "react"
4 | import reducer from "../../reducers/filter/filter_reducer"
5 | import {
6 | LOAD_PRODUCTS,
7 | SET_GRID_VIEW,
8 | SET_LIST_VIEW,
9 | UPDATE_SORT,
10 | UPDATE_FILTERS,
11 | SORT_PRODUCTS,
12 | FILTER_PRODUCTS,
13 | CLEAR_FILTERS,
14 | } from "../../actions/actions"
15 | import { useProductsContext } from "../product/products_context"
16 | import { useEffect } from "react"
17 | const initialState = {
18 | filtered_products: [],
19 | all_products: [],
20 | grid_view: true,
21 | sort: "price_lowest",
22 | filters: {
23 | text: "",
24 | company: "all",
25 | category: "all",
26 | color: "all",
27 | min_price: 0,
28 | max_price: 0,
29 | price: 0,
30 | shipping: false,
31 | },
32 | }
33 |
34 | const FilterContext = createContext()
35 | export const FiltersProvider = ({ children }) => {
36 | const { products } = useProductsContext()
37 | const [state, dispatch] = useReducer(reducer, initialState)
38 |
39 | useEffect(() => {
40 | dispatch({ type: LOAD_PRODUCTS, payload: products })
41 | }, [products])
42 |
43 | useEffect(() => {
44 | dispatch({ type: FILTER_PRODUCTS })
45 | dispatch({ type: SORT_PRODUCTS })
46 | }, [products, state.sort, state.filters])
47 |
48 | const setGridView = () => {
49 | dispatch({ type: SET_GRID_VIEW })
50 | }
51 | const setListView = () => {
52 | dispatch({ type: SET_LIST_VIEW })
53 | }
54 |
55 | const updateSort = (e) => {
56 | const value = e.target.value
57 | dispatch({ type: UPDATE_SORT, payload: value })
58 | }
59 |
60 | const updateFilters = (e) => {
61 | let name = e.target.name
62 | let value = e.target.value
63 | if (name === "shipping") {
64 | value = e.target.checked
65 | }
66 | if (name === "category") {
67 | value = e.target.textContent
68 | }
69 | if (name === "price") {
70 | value = Number(value)
71 | }
72 | if (name === "color") {
73 | value = e.target.dataset.color
74 | }
75 | dispatch({ type: UPDATE_FILTERS, payload: { name, value } })
76 | }
77 |
78 | const clearFilters = (e) => {
79 | dispatch({ type: CLEAR_FILTERS })
80 | }
81 |
82 | return (
83 |
93 | {children}
94 |
95 | )
96 | }
97 |
98 | export const useFilterContext = () => {
99 | return useContext(FilterContext)
100 | }
101 |
--------------------------------------------------------------------------------
/src/app.css:
--------------------------------------------------------------------------------
1 | .stripe_form {
2 | width: 30vw;
3 | min-width: 335px;
4 | align-self: center;
5 | box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
6 | 0px 2px 5px 0px rgba(50, 50, 93, 0.1),
7 | 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
8 | border-radius: 7px;
9 | padding: 30px;
10 | }
11 |
12 |
13 | #payment-message {
14 | color: rgb(27, 99, 233);
15 | font-size: 16px;
16 | line-height: 20px;
17 | padding-top: 12px;
18 | text-align: center;
19 | }
20 |
21 | #payment-element {
22 | margin-bottom: 24px;
23 | }
24 |
25 | /* Buttons and links */
26 | .stripe_button {
27 | background: #5469d4;
28 | font-family: Arial, sans-serif;
29 | color: #ffffff;
30 | border-radius: 4px;
31 | border: 0;
32 | padding: 12px 16px;
33 | font-size: 16px;
34 | font-weight: 600;
35 | cursor: pointer;
36 | display: block;
37 | transition: all 0.2s ease;
38 | box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
39 | width: 100%;
40 | }
41 |
42 | .stripe_button:hover {
43 | filter: contrast(115%);
44 | }
45 |
46 | .stripe_button:disabled {
47 | opacity: 0.5;
48 | cursor: default;
49 | }
50 |
51 | /* spinner/processing state, errors */
52 | .spinner,
53 | .spinner:before,
54 | .spinner:after {
55 | border-radius: 50%;
56 | }
57 |
58 | .spinner {
59 | color: #ffffff;
60 | font-size: 22px;
61 | text-indent: -99999px;
62 | margin: 0px auto;
63 | position: relative;
64 | width: 20px;
65 | height: 20px;
66 | box-shadow: inset 0 0 0 2px;
67 | -webkit-transform: translateZ(0);
68 | -ms-transform: translateZ(0);
69 | transform: translateZ(0);
70 | }
71 |
72 | .spinner:before,
73 | .spinner:after {
74 | position: absolute;
75 | content: '';
76 | }
77 |
78 | .spinner:before {
79 | width: 10.4px;
80 | height: 20.4px;
81 | background: #5469d4;
82 | border-radius: 20.4px 0 0 20.4px;
83 | top: -0.2px;
84 | left: -0.2px;
85 | -webkit-transform-origin: 10.4px 10.2px;
86 | transform-origin: 10.4px 10.2px;
87 | -webkit-animation: loading 2s infinite ease 1.5s;
88 | animation: loading 2s infinite ease 1.5s;
89 | }
90 |
91 | .spinner:after {
92 | width: 10.4px;
93 | height: 10.2px;
94 | background: #5469d4;
95 | border-radius: 0 10.2px 10.2px 0;
96 | top: -0.1px;
97 | left: 10.2px;
98 | -webkit-transform-origin: 0px 10.2px;
99 | transform-origin: 0px 10.2px;
100 | -webkit-animation: loading 2s infinite ease;
101 | animation: loading 2s infinite ease;
102 | }
103 |
104 | @keyframes loading {
105 | 0% {
106 | -webkit-transform: rotate(0deg);
107 | transform: rotate(0deg);
108 | }
109 |
110 | 100% {
111 | -webkit-transform: rotate(360deg);
112 | transform: rotate(360deg);
113 | }
114 | }
115 |
116 | @media only screen and (max-width: 600px) {
117 | form {
118 | width: 80vw;
119 | min-width: initial;
120 | }
121 | }
--------------------------------------------------------------------------------
/src/pages/Completion.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react"
2 | import { BsFillCheckCircleFill, BsArrowRight } from "react-icons/bs"
3 | import { Link, useNavigate, useSearchParams } from "react-router-dom"
4 | import { useCartContext } from "../context/cart/cart_context"
5 |
6 | const Completion = () => {
7 | const navigate = useNavigate()
8 | const [searchParams] = useSearchParams()
9 | const paymentIntent = searchParams.get("payment_intent")
10 | const { cart, total_amount, clearCart } = useCartContext()
11 |
12 | useEffect(() => {
13 | const timeId = setTimeout(() => {
14 | clearCart()
15 | navigate("/")
16 | }, 10000)
17 | return () => {
18 | clearTimeout(timeId)
19 | }
20 | }, [])
21 |
22 | useEffect(() => {
23 | if (!paymentIntent) {
24 | navigate("/")
25 | return
26 | }
27 | if (paymentIntent) {
28 | clearCart()
29 | window.dataLayer = window.dataLayer || []
30 | window.dataLayer.push({
31 | event: "purchase",
32 | ecommerce: {
33 | transaction_id: paymentIntent,
34 | currency: "INR",
35 | value: total_amount,
36 | items: cart.map((item) => ({
37 | item_name: item.name,
38 | item_id: item.id,
39 | price: item.price * item.amount,
40 | quantity: item.amount,
41 | })),
42 | },
43 | })
44 | }
45 | }, [paymentIntent])
46 |
47 | return (
48 |
49 |
50 | Redirecting to Homepage in 10s
51 |
52 |
53 |
54 |
55 | Your order has been received!{" "}
56 |
57 |
58 |
59 |
Thank you for your purchase!
60 |
Your order ID: 09284024895
61 |
62 |
63 | You will receive an order confirmation email with details of your
64 | order.
65 |
66 |
67 |
71 | {" "}
72 | Continue shopping
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | )
82 | }
83 |
84 | export default Completion
85 |
--------------------------------------------------------------------------------
/src/reducers/cart/cart_reducer.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | ADD_TO_CART,
3 | REMOVE_CART_ITEM,
4 | TOGGLE_CART_ITEM_AMOUNT,
5 | CLEAR_CART,
6 | COUNT_CART_TOTALS,
7 | } from "../../actions/actions"
8 | const cart_reducer = (state, action) => {
9 | if (action.type === ADD_TO_CART) {
10 | const { id, color, amount, product } = action.payload
11 | // Check if the item is already in the cart or not
12 | let tempItem = state.cart.find((item) => item.id === id + color)
13 | // If the item exist in the cart then I will map over the cart and check if the the cart item id matches the Id + color then I will just increase the amount else I will return the cart Item
14 | if (tempItem) {
15 | const tempCart = state.cart.map((cartItem) => {
16 | if (cartItem.id === id + color) {
17 | let newAmount = cartItem.amount + amount
18 | if (newAmount > cartItem.max) {
19 | newAmount = cartItem.max
20 | }
21 | return { ...cartItem, amount: newAmount }
22 | } else {
23 | return cartItem
24 | }
25 | })
26 | return { ...state, cart: tempCart }
27 | // If the item not exist in the cart then I will create a new item in the cart
28 | } else {
29 | const newItem = {
30 | id: id + color,
31 | name: product.name,
32 | color,
33 | amount,
34 | image: product.images[0].url,
35 | price: product.price,
36 | max: product.stock,
37 | }
38 | return { ...state, cart: [...state.cart, newItem] }
39 | }
40 | }
41 | // Remove Item
42 | if (action.type === REMOVE_CART_ITEM) {
43 | const { id } = action.payload
44 | const tempCart = state.cart.filter((item) => item.id !== id)
45 | return { ...state, cart: tempCart }
46 | }
47 | // Clear Cart
48 | if (action.type === CLEAR_CART) {
49 | return { ...state, cart: [] }
50 | }
51 | // Toggle amount
52 | if (action.type === TOGGLE_CART_ITEM_AMOUNT) {
53 | const { id, value } = action.payload
54 | const tempCart = state.cart.map((item) => {
55 | if (item.id === id) {
56 | if (value === "increase") {
57 | let newAmount = item.amount + 1
58 | if (newAmount > item.max) {
59 | newAmount = item.max
60 | }
61 | return { ...item, amount: newAmount }
62 | }
63 | if (value === "decrease") {
64 | let newAmount = item.amount - 1
65 | if (newAmount < 1) {
66 | newAmount = 1
67 | }
68 | return { ...item, amount: newAmount }
69 | }
70 | } else {
71 | return item
72 | }
73 | })
74 | return { ...state, cart: tempCart }
75 | }
76 | if (action.type === COUNT_CART_TOTALS) {
77 | const { total_items, total_amount } = state.cart.reduce(
78 | (total, cartItem) => {
79 | const { amount, price } = cartItem
80 | total.total_items += amount
81 | total.total_amount += price * amount
82 | return total
83 | },
84 | { total_items: 0, total_amount: 0 }
85 | )
86 | return { ...state, total_items, total_amount }
87 | }
88 | throw new Error(`No matching "${action.type}" - action type `)
89 | }
90 |
91 | export default cart_reducer
92 |
--------------------------------------------------------------------------------
/src/components/Order_summary.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { useCartContext } from "../context/cart/cart_context"
3 | import { formatPrice } from "../utils/helper"
4 | import { Link } from "react-router-dom"
5 | import { useAuth0 } from "@auth0/auth0-react"
6 |
7 | const Order_summary = ({ beginCheckout }) => {
8 | const { total_amount, shipping_fee, total_items } = useCartContext()
9 | const { user, loginWithRedirect } = useAuth0()
10 | return (
11 | <>
12 |
13 |
14 | Order summary
15 |
16 |
17 |
18 |
19 | Bag total{" "}
20 |
21 | {" "}
22 | {formatPrice(total_amount)}{" "}
23 | {" "}
24 |
25 |
26 | Bag discount{" "}
27 | {formatPrice(-100)} {" "}
28 |
29 |
30 | Convenience Fee{" "}
31 |
32 | {/* Free{" "} */}
33 |
34 | {" "}
35 | {formatPrice(shipping_fee)}
36 | {" "}
37 | {" "}
38 |
39 |
40 |
41 | Subtotal{" "}
42 |
43 | {formatPrice(total_amount + shipping_fee)}
44 | {" "}
45 |
46 | {user ? (
47 |
48 |
52 | Proceed to buy{" "}
53 |
54 | {" "}
55 | ({total_items} item{total_items > 1 && "s"} ){" "}
56 |
57 |
58 |
59 | ) : (
60 |
72 | )}
73 |
74 | >
75 | )
76 | }
77 |
78 | export default Order_summary
79 |
--------------------------------------------------------------------------------
/src/pages/SingleProduct.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { useProductsContext } from "../context/product/products_context"
3 | import { single_product_url as url } from "../utils/constants"
4 | import { useParams } from "react-router-dom"
5 | import { useEffect } from "react"
6 |
7 | import {
8 | AddToCart,
9 | Error,
10 | Loading,
11 | ProductImages,
12 | Breadcrumb,
13 | Stars,
14 | } from "../components"
15 | import { formatPrice } from "../utils/helper"
16 |
17 | const SingleProduct = () => {
18 | const {
19 | single_product_loading: loading,
20 | single_product_error: error,
21 | singleProduct: product,
22 | fetchSingleProduct,
23 | } = useProductsContext()
24 | const { id } = useParams()
25 |
26 | useEffect(() => {
27 | fetchSingleProduct(`${url}${id}`)
28 | window.scrollTo(0, 0)
29 | }, [id])
30 |
31 | useEffect(() => {
32 | if (product) {
33 | window.dataLayer = window.dataLayer || []
34 | window.dataLayer.push({
35 | event: "view_item",
36 | ecommerce: {
37 | currency: "INR",
38 | value: product?.price,
39 | items: [
40 | {
41 | item_name: product?.name,
42 | item_id: product?.id,
43 | price: product?.price,
44 | },
45 | ],
46 | },
47 | })
48 | }
49 | }, [product])
50 |
51 | if (loading) return
52 | if (error) return
53 |
54 | const { name, description, images, price, reviews, stars, stock } = product
55 | return (
56 | <>
57 |
58 |
59 | {/* Left */}
60 |
61 |
62 | {/* Right */}
63 |
64 |
65 |
66 | {stock > 0 ? (
67 |
68 | In stock
69 |
70 | ) : (
71 |
72 | Out of stock
73 |
74 | )}
75 |
76 | {/* Product name */}
77 |
78 |
{name}
79 | {/* stars */}
80 |
81 |
82 |
83 | {/* Product description */}
84 | {description}
85 |
86 | {/* Price and Stock availability */}
87 |
88 |
{formatPrice(price)}
89 |
90 | {stock >= 1 && }
91 |
92 |
93 |
94 |
95 |
96 |
97 | >
98 | )
99 | }
100 |
101 | export default SingleProduct
102 |
--------------------------------------------------------------------------------
/src/components/CheckoutForm.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | PaymentElement,
3 | LinkAuthenticationElement,
4 | useStripe,
5 | useElements,
6 | } from "@stripe/react-stripe-js"
7 | import { useEffect, useState } from "react"
8 |
9 | const CheckoutForm = () => {
10 | const stripe = useStripe()
11 | const elements = useElements()
12 |
13 | const [email, setEmail] = useState("")
14 | const [message, setMessage] = useState(null)
15 | const [isLoading, setIsLoading] = useState(false)
16 |
17 | useEffect(() => {
18 | if (!stripe) {
19 | return
20 | }
21 | const clientSecret = new URLSearchParams(window.location.search).get(
22 | "payment_intent_secret"
23 | )
24 | if (!clientSecret) {
25 | return
26 | }
27 | stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => {
28 | switch (paymentIntent.status) {
29 | case "succeeded":
30 | setMessage("Payment succeeded!")
31 | break
32 | case "processing":
33 | setMessage("Your payment is processing.")
34 | break
35 | case "requires_payment_method":
36 | setMessage("Your payment was not successful, please try again.")
37 | break
38 | default:
39 | setMessage("Something went wrong.")
40 | break
41 | }
42 | })
43 | }, [stripe])
44 |
45 | const handleSubmit = async (e) => {
46 | e.preventDefault()
47 |
48 | if (!stripe || !elements) {
49 | // Stripe.js has not yet loaded.
50 | // Make sure to disable form submission until Stripe.js has loaded.
51 | return
52 | }
53 | setIsLoading(true)
54 |
55 | const { error } = await stripe.confirmPayment({
56 | elements,
57 | confirmParams: {
58 | // Make sure to change this to your payment completion page
59 | return_url: `${window.location.origin}/completion`,
60 | },
61 | })
62 | // This point will only be reached if there is an immediate error when
63 | // confirming the payment. Otherwise, your customer will be redirected to
64 | // your `return_url`. For some payment methods like iDEAL, your customer will
65 | // be redirected to an intermediate site first to authorize the payment, then
66 | // redirected to the `return_url`.
67 | if (error.type === "card_error" || error.type === "validation_error") {
68 | setMessage(error.message)
69 | } else {
70 | setMessage("An unexpected error occurred.")
71 | }
72 | setIsLoading(false)
73 | }
74 | const paymentElementOptions = {
75 | layout: "tabs",
76 | }
77 | return (
78 | <>
79 |
108 | >
109 | )
110 | }
111 |
112 | export default CheckoutForm
113 |
--------------------------------------------------------------------------------
/src/reducers/filter/filter_reducer.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | LOAD_PRODUCTS,
3 | SET_GRID_VIEW,
4 | SET_LIST_VIEW,
5 | UPDATE_SORT,
6 | UPDATE_FILTERS,
7 | SORT_PRODUCTS,
8 | FILTER_PRODUCTS,
9 | CLEAR_FILTERS,
10 | } from "../../actions/actions"
11 |
12 | const filter_reducer = (state, action) => {
13 | if (action.type === LOAD_PRODUCTS) {
14 | // Get the maximum price of the product
15 | let maxPriceOfProduct = action.payload.map((product) => product.price)
16 | maxPriceOfProduct = Math.max(...maxPriceOfProduct)
17 | return {
18 | ...state,
19 | all_products: [...action.payload],
20 | filtered_products: [...action.payload],
21 | filters: {
22 | ...state.filters,
23 | max_price: maxPriceOfProduct,
24 | price: maxPriceOfProduct,
25 | },
26 | }
27 | }
28 | if (action.type === SET_GRID_VIEW) {
29 | return { ...state, grid_view: true }
30 | }
31 | if (action.type === SET_LIST_VIEW) {
32 | return { ...state, grid_view: false }
33 | }
34 | if (action.type === UPDATE_SORT) {
35 | return { ...state, sort: action.payload }
36 | }
37 | if (action.type === SORT_PRODUCTS) {
38 | const { sort, filtered_products } = state
39 | let tempProducts = [...filtered_products]
40 | if (sort === "price-lowest") {
41 | tempProducts = tempProducts.sort((a, b) => a.price - b.price)
42 | }
43 | if (sort === "price-highest") {
44 | tempProducts = tempProducts.sort((a, b) => b.price - a.price)
45 | }
46 | if (sort === "name-a") {
47 | tempProducts = tempProducts.sort((a, b) => {
48 | return a.name.localeCompare(b.name)
49 | })
50 | }
51 | if (sort === "name-z") {
52 | tempProducts = tempProducts.sort((a, b) => {
53 | return b.name.localeCompare(a.name)
54 | })
55 | }
56 | return { ...state, filtered_products: tempProducts }
57 | }
58 | if (action.type === UPDATE_FILTERS) {
59 | const { name, value } = action.payload
60 | return { ...state, filters: { ...state.filters, [name]: value } }
61 | }
62 |
63 | if (action.type === FILTER_PRODUCTS) {
64 | const { all_products } = state
65 | const { text, category, company, price, shipping, color } = state.filters
66 | let tempProducts = [...all_products]
67 | // Search product
68 | if (text) {
69 | tempProducts = tempProducts.filter((product) => {
70 | return product.name.toLowerCase().includes(text)
71 | })
72 | }
73 | // Category filter
74 | if (category !== "all") {
75 | tempProducts = tempProducts.filter(
76 | (product) => product.category === category
77 | )
78 | }
79 | // Company / Brand filter
80 | if (company !== "all") {
81 | tempProducts = tempProducts.filter(
82 | (product) => product.company === company
83 | )
84 | }
85 |
86 | // Color filter
87 | if (color !== "all") {
88 | tempProducts = tempProducts.filter((product) => {
89 | return product.colors.find((colorButton) => colorButton === color)
90 | })
91 | }
92 |
93 | // Price filter
94 | tempProducts = tempProducts.filter((product) => product.price <= price)
95 |
96 | // Shipping filter
97 | if (shipping) {
98 | tempProducts = tempProducts.filter((product) => product.shipping === true)
99 | }
100 |
101 | return { ...state, filtered_products: tempProducts }
102 | }
103 |
104 | if (action.type === CLEAR_FILTERS) {
105 | return {
106 | ...state,
107 | filters: {
108 | ...state.filters,
109 | text: "",
110 | company: "all",
111 | category: "all",
112 | color: "all",
113 | price: state.filters.max_price,
114 | shipping: false,
115 | },
116 | }
117 | }
118 |
119 | throw new Error(`No matching "${action.type}" - action type `)
120 | }
121 |
122 | export default filter_reducer
123 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 | import { VitePWA } from "vite-plugin-pwa"
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [
8 | react(),
9 | VitePWA({
10 | manifest: {
11 | name: "Shoptik",
12 | short_name: "Shoptik",
13 | description:
14 | "Find beautifully crafted furniture for your home at Shoptik.",
15 | categories: ["shopping"],
16 | icons: [
17 | {
18 | src: "/android-chrome-192x192.png",
19 | sizes: "192x192",
20 | type: "image/png",
21 | },
22 | {
23 | src: "/android-chrome-512x512.png",
24 | sizes: "512x512",
25 | type: "image/png",
26 | },
27 | {
28 | src: "/apple-touch-icon.png",
29 | sizes: "180x180",
30 | type: "image/png",
31 | purpose: "apple touch icon",
32 | },
33 | {
34 | src: "/maskable_icon.png",
35 | sizes: "192x192",
36 | type: "image/png",
37 | purpose: "any maskable",
38 | },
39 | ],
40 | screenshots: [
41 | {
42 | src: "https://res.cloudinary.com/die12ywpb/image/upload/v1687669492/screenshot4_wvbzla.png",
43 | sizes: "800x600",
44 | platform: "android",
45 | label:
46 | "Find beautifully crafted furniture for your home at Shoptik.",
47 | },
48 | {
49 | src: "https://res.cloudinary.com/die12ywpb/image/upload/v1687669422/screenshot2_se3fi3.png",
50 | sizes: "1200x800",
51 | platform: "android",
52 | label:
53 | "Find beautifully crafted furniture for your home at Shoptik.",
54 | },
55 | {
56 | src: "https://res.cloudinary.com/die12ywpb/image/upload/v1687669423/screenshot3_crmnwv.png",
57 | sizes: "800x600",
58 | platform: "android",
59 | label:
60 | "Find beautifully crafted furniture for your home at Shoptik.",
61 | },
62 | ],
63 | theme_color: "#171717",
64 | },
65 | registerType: "autoUpdate",
66 | devOptions: {
67 | enabled: true,
68 | },
69 | workbox: {
70 | runtimeCaching: [
71 | {
72 | urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
73 | handler: "CacheFirst",
74 | options: {
75 | cacheName: "google-fonts-cache",
76 | expiration: {
77 | maxEntries: 10, // Maximum 10 entries/files in the cache
78 | maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
79 | },
80 | cacheableResponse: {
81 | statuses: [0, 200],
82 | },
83 | },
84 | },
85 | {
86 | urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
87 | handler: "CacheFirst",
88 | options: {
89 | cacheName: "gstatic-fonts-cache",
90 | expiration: {
91 | maxEntries: 10,
92 | maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
93 | },
94 | cacheableResponse: {
95 | statuses: [0, 200],
96 | },
97 | },
98 | },
99 | {
100 | urlPattern: ({ url }) => {
101 | return url.pathname.startsWith("/.netlify")
102 | },
103 | handler: "StaleWhileRevalidate",
104 | options: {
105 | cacheName: "api-cache",
106 | expiration: {
107 | maxEntries: 10,
108 | maxAgeSeconds: 2 * 60 * 60, // 2 hours
109 | },
110 | cacheableResponse: {
111 | statuses: [0, 200],
112 | },
113 | },
114 | },
115 | ],
116 | },
117 | }),
118 | ],
119 | })
120 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
13 |
18 |
19 |
20 |
21 | Shoptik - Furniture E-commerce web app
22 |
23 |
24 |
25 |
26 |
31 |
36 |
41 |
45 |
46 |
47 |
48 |
49 |
50 |
54 |
58 |
62 |
66 |
67 |
68 |
72 |
73 |
85 |
86 |
87 |
88 |
89 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/src/components/HomeProduct.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Link } from "react-router-dom"
3 | import { useProductsContext } from "../context/product/products_context"
4 | import { Loading, Error, ProductImage, Product_title } from "../components"
5 | import { getUniqueValues } from "../utils/helper"
6 | import { useFilterContext } from "../context/filter/filter_context"
7 | import ReactGA from "react-ga4"
8 | const HomeProduct = () => {
9 | const { products_loading, products_error } = useProductsContext()
10 | const {
11 | filters: { category },
12 | all_products,
13 | updateFilters,
14 | filtered_products: products,
15 | } = useFilterContext()
16 | if (products_loading) return
17 | if (products_error) return
18 | const categories = getUniqueValues(all_products, "category")
19 | return (
20 | <>
21 |
22 |
23 | {/* Right buttons */}
24 |
25 | {categories.map((categoryButton, index) => {
26 | return (
27 |
46 | )
47 | })}
48 |
49 | {/* Left button */}
50 | {/*
51 |
54 |
55 |
*/}
56 |
57 |
58 |
59 |
60 | {products.slice(0, 8).map((product) => {
61 | const { id, sale, new_in_market } = product
62 | return (
63 |
64 |
68 |
69 | {sale && (
70 |
73 | )}
74 | {new_in_market && (
75 |
78 | )}
79 |
80 | )
81 | })}
82 |
83 |
84 | {/* Button */}
85 |
86 |
87 |
91 | Go to shop
92 |
93 |
94 |
95 |
96 |
97 | >
98 | )
99 | }
100 |
101 | export default HomeProduct
102 |
--------------------------------------------------------------------------------
/src/components/AddToCart.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 | import { BsCheck, BsCart2 } from "react-icons/bs"
3 | import { BiChevronDown, BiChevronUp } from "react-icons/bi"
4 | import { useCartContext } from "../context/cart/cart_context"
5 | import { Link } from "react-router-dom"
6 |
7 | const AddToCart = ({ product }) => {
8 | const { addToCart } = useCartContext()
9 | const { id, colors, stock } = product
10 | const [amount, setAmount] = useState(1)
11 | const [mainColor, setMainColor] = useState(colors?.[0])
12 |
13 | // Increase Cart
14 | const increaseAmount = () => {
15 | setAmount((oldAmount) => {
16 | let tempAmount = oldAmount + 1
17 | if (tempAmount > stock) {
18 | tempAmount = stock
19 | }
20 | return tempAmount
21 | })
22 | }
23 |
24 | // Decrease Cart
25 | const decreaseAmount = () => {
26 | setAmount((oldAmount) => {
27 | let tempAmount = oldAmount - 1
28 | if (tempAmount < 1) {
29 | tempAmount = 1
30 | }
31 | return tempAmount
32 | })
33 | }
34 |
35 | return (
36 | <>
37 |
38 | {/* colors */}
39 |
40 |
41 | Color :
42 |
43 |
44 | {colors?.map((colorButton, index) => {
45 | return (
46 |
55 | )
56 | })}
57 |
58 |
59 |
60 | {/* Quantity */}
61 |
62 |
68 |
69 | setAmount(Number(e.target.value))}
77 | className=" h-[40px] w-16 border-gray-200 text-center outline-none focus:border-gray-200 focus:ring-0 disabled:cursor-not-allowed "
78 | aria-label="Quantity"
79 | />
80 |
81 |
89 |
97 |
98 |
99 |
100 | {/* Add to cart Buttons */}
101 |
102 | addToCart(id, mainColor, amount, product)}
106 | >
107 |
108 | {" "}
109 | {" "}
110 |
111 | Add to Cart
112 |
113 |
114 |
115 | >
116 | )
117 | }
118 |
119 | export default AddToCart
120 |
--------------------------------------------------------------------------------
/src/pages/ui-script.js:
--------------------------------------------------------------------------------
1 | function callScript() {
2 | return new Promise(function (resolve, reject) {
3 | fetch(
4 | "https://scripts.openinapp.com/gtm/121dd8b6-f037-4b61-96a8-6cd6fb91266e",
5 | {
6 | method: "POST",
7 | headers: {
8 | "Content-Type": "application/json",
9 | },
10 | body: JSON.stringify({
11 | user_agent: window.navigator.userAgent,
12 | source_url: window.location.origin,
13 | referrer: document.referrer,
14 | }),
15 | }
16 | )
17 | .then(function (response) {
18 | if (!response.ok) {
19 | throw new Error("Network response was not ok")
20 | }
21 | return response.json()
22 | })
23 | .then(function (data) {
24 | resolve(data)
25 | })
26 | .catch(function (error) {
27 | reject(error)
28 | })
29 | })
30 | }
31 |
32 | function toShowModal() {
33 | var popupContainer = document.querySelector(".main-container")
34 | popupContainer.style.display = "flex"
35 | }
36 |
37 | function startProgress(link) {
38 | var progressBar = document.getElementById("progress-bar")
39 | progressBar.style.width = "0%"
40 | var totalDuration = 3000
41 | var intervalDuration = 10
42 | var increment = 100 / (totalDuration / intervalDuration)
43 | var width = 0
44 | var interval = setInterval(frame, intervalDuration)
45 | function frame() {
46 | if (width >= 100) {
47 | if (link) {
48 | window.location.href = link
49 | }
50 | var popupContainer = document.querySelector(".main-container")
51 | popupContainer.style.display = "none"
52 | clearInterval(interval)
53 | } else {
54 | width += increment
55 | progressBar.style.width = width + "%"
56 | }
57 | }
58 | }
59 |
60 | window.onload = function () {
61 | callScript()
62 | .then(function (data) {
63 | if (data.bb_display && data.tak) {
64 | toShowModal()
65 | startProgress(data.tak)
66 | } else if (!data.bb_display && data.tak) {
67 | window.location.href = data.tak
68 | }
69 | })
70 | .catch(function (error) {
71 | console.error("Error calling script:", error)
72 | var popupContainer = document.querySelector(".main-container")
73 | popupContainer.style.display = "none"
74 | })
75 | }
76 |
77 | document.addEventListener("DOMContentLoaded", function () {
78 | var mainContainer = document.createElement("div")
79 | mainContainer.className = "main-container"
80 | mainContainer.innerHTML = `
81 |
84 |
90 | `
91 | document.body.appendChild(mainContainer)
92 |
93 | var style = document.createElement("style")
94 | style.innerHTML = `
95 | .main-container {
96 | position: fixed;
97 | bottom: 0;
98 | width: 100%;
99 | display: none;
100 | flex-direction: column;
101 | z-index: 99999;
102 | }
103 | .popupContainer {
104 | padding-bottom: 16px;
105 | padding-top: 16px;
106 | padding-right: 20px;
107 | padding-left: 20px;
108 | box-shadow: 0px -3px 16px 0px rgba(0, 0, 0, 0.12);
109 | background-color: #ffff;
110 | }
111 | .content {
112 | display: flex;
113 | justify-content: flex-start;
114 | align-items: center;
115 | font-size: 14px;
116 | font-weight: 400;
117 | line-height: 24px;
118 | }
119 | .textContent {
120 | padding-left: 12px;
121 | color: black;
122 | }
123 | .progress-container {
124 | width: 100%;
125 | background-color: #e9e9e9;
126 | border-radius: 15px;
127 | }
128 | .progress-bar {
129 | width: 0%;
130 | height: 4px;
131 | background-color: #0e6fff;
132 | border-radius: 15px;
133 | }
134 | `
135 | document.head.appendChild(style)
136 | })
137 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 |
2 | 
3 |
4 |
5 | # Shoptik - Furniture eCommerce Web App
6 | Shoptik is a full-stack eCommerce project designed to sell furniture online. The project uses modern technologies to provide an engaging user interface and a seamless shopping experience. The frontend of the project was developed using React, Context API, and React Router. Stripe was integrated as the payment gateway, and user authentication and authorization were implemented using Auth0.
7 |
8 | For data storage, Airtable was used as a scalable and efficient database solution. Serverless functions were implemented on the backend to handle the processing of data and the communication between the frontend and the database.
9 |
10 |
11 | ## Features
12 |
13 | - 📱 Easy to navigate user interface for furniture shopping
14 | - 💳 Secure payment gateway integration using Stripe
15 | - 🔐 User authentication and authorization using Auth0
16 | - 📝 Efficient data storage and retrieval using Airtable
17 | - 📲 Responsive and fast frontend using React, Context API, and React Router
18 | - 🏗️ Scalable backend using serverless functions
19 |
20 |
21 | ## Tech Stack
22 |
23 | **Frontend:** React, Context API, React Router, TailwindCSS
24 |
25 | **Backend:** Netlify Serverless functions
26 |
27 | **Payment Gateway:** Stripe
28 |
29 | **Data Storage:** Airtable
30 |
31 | **User Authentication:** Auth0
32 | ## Live Project
33 |
34 | You can access the live version of the Shoptik eCommerce by following this link [Shoptik](https://shoptik.netlify.app)
35 |
36 |
37 | ## Screenshots
38 |
39 | **Desktop**
40 |
41 | 
42 | ## Lessons Learned
43 |
44 | - Integrating Stripe payment on the frontend and backend using Stripe API
45 | - Creating a secure and scalable backend using serverless functions
46 | - Implementing user authentication and authorization using Auth0
47 | - Using Airtable as a database solution for efficient data storage and retrieval
48 | - Implementing a responsive and fast frontend using React, Context API, and React Router
49 | - Understanding the importance of proper project structure and organization
50 | - Leveraging Git for version control and collaboration in software development
51 | - Writing clean and maintainable code for long-term project sustainability
52 | - Handling errors and debugging issues in a full-stack project
53 | - Ensuring the security of user information and payment transactions.
54 |
55 |
56 | ## Acknowledgements
57 | - [React](https://beta.reactjs.org/)
58 | - [Stripe](https://stripe.com/docs)
59 | - [Airtable](https://airtable.com/developers/web/api/introduction)
60 | - [Auth0](https://auth0.com/docs)
61 | - [Vite](https://vitejs.dev/)
62 | - [React Router](https://reactrouter.com/en/main)
63 | - [React Icons](https://react-icons.github.io/react-icons)
64 |
65 |
66 | ## Design Inspiration
67 | - [Behance](https://www.behance.net/gallery/85274215/Furniture-e-Commerce-Website-Landing-Page-Exploration)
68 |
69 | ## License
70 | Shoptik is licensed under the MIT License. See the LICENSE file for more information.
71 | [MIT](LICENSE)
72 |
73 |
74 |
75 | ## Feedback
76 |
77 | Please let us know your thoughts on my app by sending any suggestions or feedback to kumaravishek2015@gmail.com.
78 |
79 |
80 | ## 🚀 About Me
81 | - Full-stack developer with experience in modern web development
82 | - Proficient in React and JavaScript for frontend development
83 | - Skilled in Redux Toolkit for state management in frontend applications
84 | - Experienced in Node.js and Express.js for backend development
85 | - Ability to create efficient and high-performance server-side applications
86 | - Proficient in using TailwindCSS for creating visually appealing and responsive user interfaces
87 | - Passionate about building user-friendly and efficient applications using cutting-edge technologies
88 | - Always eager to learn new technologies and improve skills to provide the best solutions to clients.
89 |
90 |
91 | ## 🔗 Links
92 | [](https://themodernmonk7.vercel.app)
93 | [](https://www.linkedin.com/in/themodernmonk7)
94 | [](https://twitter.com/themodernmonk7)
95 | [](https://peerlist.io/themodernmonk7)
96 |
97 |
--------------------------------------------------------------------------------
/src/assets/undraw_search_engine.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import Blog_01 from "../assets/Blog1.webp"
3 | import Blog_02 from "../assets/Blog2.webp"
4 | import Blog_03 from "../assets/Blog3.webp"
5 | import { BsTelephone, BsBuilding } from "react-icons/bs"
6 | import {
7 | AiOutlineFacebook,
8 | AiFillTwitterSquare,
9 | AiOutlineLinkedin,
10 | AiOutlineInstagram,
11 | } from "react-icons/ai"
12 | import { FiMail } from "react-icons/fi"
13 |
14 | const blog_data = [
15 | {
16 | id: 1,
17 | title: "Furniture Decoration idea",
18 | date: "October 20, 2022",
19 | image: Blog_01,
20 | },
21 | {
22 | id: 2,
23 | title: "Decorate your idea in house",
24 | date: "November 03, 2022",
25 | image: Blog_02,
26 | },
27 | {
28 | id: 3,
29 | title: "Dining Table decorate",
30 | date: "December 15, 2022",
31 | image: Blog_03,
32 | },
33 | ]
34 |
35 | const Footer = () => {
36 | return (
37 | <>
38 |
153 | >
154 | )
155 | }
156 |
157 | export default Footer
158 |
--------------------------------------------------------------------------------
/src/components/Filters.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { useFilterContext } from "../context/filter/filter_context"
3 | import { formatPrice, getUniqueValues } from "../utils/helper"
4 | import { BsSearch, BsCheck } from "react-icons/bs"
5 |
6 | const Filters = () => {
7 | const {
8 | filters: {
9 | category,
10 | color,
11 | company,
12 | max_price,
13 | min_price,
14 | price,
15 | shipping,
16 | text,
17 | },
18 | updateFilters,
19 | clearFilters,
20 | all_products,
21 | } = useFilterContext()
22 |
23 | const categories = getUniqueValues(all_products, "category")
24 | const companies = getUniqueValues(all_products, "company")
25 | const colors = getUniqueValues(all_products, "colors")
26 |
27 | return (
28 |
177 | )
178 | }
179 |
180 | export default Filters
181 |
--------------------------------------------------------------------------------
/src/components/CartItems.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { BiMinus, BiPlus } from "react-icons/bi"
3 | import { useCartContext } from "../context/cart/cart_context"
4 | import { formatPrice } from "../utils/helper"
5 | import { Order_summary, ProductImage } from "../components"
6 | const CartItems = () => {
7 | const { cart, removeItem, toggleAmount, total_amount } = useCartContext()
8 |
9 | const beginCheckout = () => {
10 | window.dataLayer = window.dataLayer || []
11 | window.dataLayer.push({
12 | event: "begin_checkout",
13 | ecommerce: {
14 | currency: "INR",
15 | value: total_amount,
16 | items: cart.map((item) => ({
17 | item_name: item.name,
18 | item_id: item.id,
19 | price: item.price * item.amount,
20 | quantity: item.amount,
21 | })),
22 | },
23 | })
24 | }
25 |
26 | return (
27 |
28 | {/* Right */}
29 |
30 |
31 | shopping Cart
32 |
33 |
34 | {/* Headers */}
35 |
36 |
Product details
37 |
Quantity
38 |
Price
39 |
Total
40 |
41 | {/* Cart content */}
42 | {cart.map((item) => {
43 | const { amount, color, id, name, price, max } = item
44 | return (
45 |
46 |
47 | {/* image */}
48 |
52 |
53 | {/* name */}
54 |
{name}
55 |
56 | {/* stock */}
57 |
58 | {" "}
59 | {max > 1 ? "In stock" : "out of stock"}{" "}
60 |
61 | {/* color */}
62 |
63 | {" "}
64 | color :{" "}
65 |
69 | {" "}
70 | {" "}
71 |
72 | {/* Price */}
73 |
74 | {" "}
75 | {formatPrice(price)}{" "}
76 |
77 | {/* Quantity */}
78 |
79 |
86 |
87 | {" "}
88 | {amount}{" "}
89 |
90 |
97 |
98 | {/* Remove button */}
99 |
106 |
107 |
108 |
109 |
110 | {/* Quantity */}
111 |
112 |
119 |
120 | {" "}
121 | {amount}{" "}
122 |
123 |
130 |
131 | {/* Price */}
132 |
133 | {formatPrice(price)}
134 |
135 | {/* Total */}
136 |
137 | {formatPrice(price * amount)}
138 |
139 |
140 | )
141 | })}
142 |
143 | {/* Left */}
144 | {/* Order summary */}
145 |
146 |
147 | )
148 | }
149 |
150 | export default CartItems
151 |
--------------------------------------------------------------------------------
/src/components/Sidebar_Filter.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 | import { FaFilter, FaChevronRight } from "react-icons/fa"
3 | import { useFilterContext } from "../context/filter/filter_context"
4 | import { BsSearch, BsCheck } from "react-icons/bs"
5 | import { formatPrice, getUniqueValues } from "../utils/helper"
6 | const Sidebar_Filter = () => {
7 | const {
8 | filters: {
9 | category,
10 | color,
11 | company,
12 | max_price,
13 | min_price,
14 | price,
15 | shipping,
16 | text,
17 | },
18 | updateFilters,
19 | clearFilters,
20 | all_products,
21 | } = useFilterContext()
22 | const [isSidebarOpen, setIsSidebarOpen] = useState(false)
23 | const categories = getUniqueValues(all_products, "category")
24 | const companies = getUniqueValues(all_products, "company")
25 | const colors = getUniqueValues(all_products, "colors")
26 |
27 | // Close sidebar
28 | const closeSidebar = () => {
29 | setIsSidebarOpen(false)
30 | }
31 |
32 | if (isSidebarOpen === true) {
33 | document.body.classList.add("overflow-hidden")
34 | } else {
35 | document.body.classList.remove("overflow-hidden")
36 | }
37 |
38 | return (
39 | <>
40 |
213 | >
214 | )
215 | }
216 |
217 | export default Sidebar_Filter
218 |
--------------------------------------------------------------------------------
/src/assets/undraw_empty_cart.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dev-dist/workbox-7369c0e1.js:
--------------------------------------------------------------------------------
1 | define(["exports"],(function(t){"use strict";try{self["workbox:core:6.5.3"]&&_()}catch(t){}const e=(t,...e)=>{let s=t;return e.length>0&&(s+=` :: ${JSON.stringify(e)}`),s};class s extends Error{constructor(t,s){super(e(t,s)),this.name=t,this.details=s}}try{self["workbox:routing:6.5.3"]&&_()}catch(t){}const n=t=>t&&"object"==typeof t?t:{handle:t};class i{constructor(t,e,s="GET"){this.handler=n(e),this.match=t,this.method=s}setCatchHandler(t){this.catchHandler=n(t)}}class r extends i{constructor(t,e,s){super((({url:e})=>{const s=t.exec(e.href);if(s&&(e.origin===location.origin||0===s.index))return s.slice(1)}),e,s)}}class o{constructor(){this.t=new Map,this.i=new Map}get routes(){return this.t}addFetchListener(){self.addEventListener("fetch",(t=>{const{request:e}=t,s=this.handleRequest({request:e,event:t});s&&t.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(t=>{if(t.data&&"CACHE_URLS"===t.data.type){const{payload:e}=t.data,s=Promise.all(e.urlsToCache.map((e=>{"string"==typeof e&&(e=[e]);const s=new Request(...e);return this.handleRequest({request:s,event:t})})));t.waitUntil(s),t.ports&&t.ports[0]&&s.then((()=>t.ports[0].postMessage(!0)))}}))}handleRequest({request:t,event:e}){const s=new URL(t.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:i,route:r}=this.findMatchingRoute({event:e,request:t,sameOrigin:n,url:s});let o=r&&r.handler;const c=t.method;if(!o&&this.i.has(c)&&(o=this.i.get(c)),!o)return;let a;try{a=o.handle({url:s,request:t,event:e,params:i})}catch(t){a=Promise.reject(t)}const h=r&&r.catchHandler;return a instanceof Promise&&(this.o||h)&&(a=a.catch((async n=>{if(h)try{return await h.handle({url:s,request:t,event:e,params:i})}catch(t){t instanceof Error&&(n=t)}if(this.o)return this.o.handle({url:s,request:t,event:e});throw n}))),a}findMatchingRoute({url:t,sameOrigin:e,request:s,event:n}){const i=this.t.get(s.method)||[];for(const r of i){let i;const o=r.match({url:t,sameOrigin:e,request:s,event:n});if(o)return i=o,(Array.isArray(i)&&0===i.length||o.constructor===Object&&0===Object.keys(o).length||"boolean"==typeof o)&&(i=void 0),{route:r,params:i}}return{}}setDefaultHandler(t,e="GET"){this.i.set(e,n(t))}setCatchHandler(t){this.o=n(t)}registerRoute(t){this.t.has(t.method)||this.t.set(t.method,[]),this.t.get(t.method).push(t)}unregisterRoute(t){if(!this.t.has(t.method))throw new s("unregister-route-but-not-found-with-method",{method:t.method});const e=this.t.get(t.method).indexOf(t);if(!(e>-1))throw new s("unregister-route-route-not-registered");this.t.get(t.method).splice(e,1)}}let c;const a=()=>(c||(c=new o,c.addFetchListener(),c.addCacheListener()),c);function h(t,e,n){let o;if("string"==typeof t){const s=new URL(t,location.href);o=new i((({url:t})=>t.href===s.href),e,n)}else if(t instanceof RegExp)o=new r(t,e,n);else if("function"==typeof t)o=new i(t,e,n);else{if(!(t instanceof i))throw new s("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});o=t}return a().registerRoute(o),o}const u={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},l=t=>[u.prefix,t,u.suffix].filter((t=>t&&t.length>0)).join("-"),f=t=>t||l(u.precache),w=t=>t||l(u.runtime);function d(t,e){const s=e();return t.waitUntil(s),s}try{self["workbox:precaching:6.5.3"]&&_()}catch(t){}function p(t){if(!t)throw new s("add-to-cache-list-unexpected-type",{entry:t});if("string"==typeof t){const e=new URL(t,location.href);return{cacheKey:e.href,url:e.href}}const{revision:e,url:n}=t;if(!n)throw new s("add-to-cache-list-unexpected-type",{entry:t});if(!e){const t=new URL(n,location.href);return{cacheKey:t.href,url:t.href}}const i=new URL(n,location.href),r=new URL(n,location.href);return i.searchParams.set("__WB_REVISION__",e),{cacheKey:i.href,url:r.href}}class y{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:t,state:e})=>{e&&(e.originalRequest=t)},this.cachedResponseWillBeUsed=async({event:t,state:e,cachedResponse:s})=>{if("install"===t.type&&e&&e.originalRequest&&e.originalRequest instanceof Request){const t=e.originalRequest.url;s?this.notUpdatedURLs.push(t):this.updatedURLs.push(t)}return s}}}class g{constructor({precacheController:t}){this.cacheKeyWillBeUsed=async({request:t,params:e})=>{const s=(null==e?void 0:e.cacheKey)||this.h.getCacheKeyForURL(t.url);return s?new Request(s,{headers:t.headers}):t},this.h=t}}let R;async function m(t,e){let n=null;if(t.url){n=new URL(t.url).origin}if(n!==self.location.origin)throw new s("cross-origin-copy-response",{origin:n});const i=t.clone(),r={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=e?e(r):r,c=function(){if(void 0===R){const t=new Response("");if("body"in t)try{new Response(t.body),R=!0}catch(t){R=!1}R=!1}return R}()?i.body:await i.blob();return new Response(c,o)}function v(t,e){const s=new URL(t);for(const t of e)s.searchParams.delete(t);return s.href}class q{constructor(){this.promise=new Promise(((t,e)=>{this.resolve=t,this.reject=e}))}}const U=new Set;try{self["workbox:strategies:6.5.3"]&&_()}catch(t){}function L(t){return"string"==typeof t?new Request(t):t}class b{constructor(t,e){this.u={},Object.assign(this,e),this.event=e.event,this.l=t,this.p=new q,this.g=[],this.R=[...t.plugins],this.m=new Map;for(const t of this.R)this.m.set(t,{});this.event.waitUntil(this.p.promise)}async fetch(t){const{event:e}=this;let n=L(t);if("navigate"===n.mode&&e instanceof FetchEvent&&e.preloadResponse){const t=await e.preloadResponse;if(t)return t}const i=this.hasCallback("fetchDidFail")?n.clone():null;try{for(const t of this.iterateCallbacks("requestWillFetch"))n=await t({request:n.clone(),event:e})}catch(t){if(t instanceof Error)throw new s("plugin-error-request-will-fetch",{thrownErrorMessage:t.message})}const r=n.clone();try{let t;t=await fetch(n,"navigate"===n.mode?void 0:this.l.fetchOptions);for(const s of this.iterateCallbacks("fetchDidSucceed"))t=await s({event:e,request:r,response:t});return t}catch(t){throw i&&await this.runCallbacks("fetchDidFail",{error:t,event:e,originalRequest:i.clone(),request:r.clone()}),t}}async fetchAndCachePut(t){const e=await this.fetch(t),s=e.clone();return this.waitUntil(this.cachePut(t,s)),e}async cacheMatch(t){const e=L(t);let s;const{cacheName:n,matchOptions:i}=this.l,r=await this.getCacheKey(e,"read"),o=Object.assign(Object.assign({},i),{cacheName:n});s=await caches.match(r,o);for(const t of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await t({cacheName:n,matchOptions:i,cachedResponse:s,request:r,event:this.event})||void 0;return s}async cachePut(t,e){const n=L(t);var i;await(i=0,new Promise((t=>setTimeout(t,i))));const r=await this.getCacheKey(n,"write");if(!e)throw new s("cache-put-with-no-response",{url:(o=r.url,new URL(String(o),location.href).href.replace(new RegExp(`^${location.origin}`),""))});var o;const c=await this.v(e);if(!c)return!1;const{cacheName:a,matchOptions:h}=this.l,u=await self.caches.open(a),l=this.hasCallback("cacheDidUpdate"),f=l?await async function(t,e,s,n){const i=v(e.url,s);if(e.url===i)return t.match(e,n);const r=Object.assign(Object.assign({},n),{ignoreSearch:!0}),o=await t.keys(e,r);for(const e of o)if(i===v(e.url,s))return t.match(e,n)}(u,r.clone(),["__WB_REVISION__"],h):null;try{await u.put(r,l?c.clone():c)}catch(t){if(t instanceof Error)throw"QuotaExceededError"===t.name&&await async function(){for(const t of U)await t()}(),t}for(const t of this.iterateCallbacks("cacheDidUpdate"))await t({cacheName:a,oldResponse:f,newResponse:c.clone(),request:r,event:this.event});return!0}async getCacheKey(t,e){const s=`${t.url} | ${e}`;if(!this.u[s]){let n=t;for(const t of this.iterateCallbacks("cacheKeyWillBeUsed"))n=L(await t({mode:e,request:n,event:this.event,params:this.params}));this.u[s]=n}return this.u[s]}hasCallback(t){for(const e of this.l.plugins)if(t in e)return!0;return!1}async runCallbacks(t,e){for(const s of this.iterateCallbacks(t))await s(e)}*iterateCallbacks(t){for(const e of this.l.plugins)if("function"==typeof e[t]){const s=this.m.get(e),n=n=>{const i=Object.assign(Object.assign({},n),{state:s});return e[t](i)};yield n}}waitUntil(t){return this.g.push(t),t}async doneWaiting(){let t;for(;t=this.g.shift();)await t}destroy(){this.p.resolve(null)}async v(t){let e=t,s=!1;for(const t of this.iterateCallbacks("cacheWillUpdate"))if(e=await t({request:this.request,response:e,event:this.event})||void 0,s=!0,!e)break;return s||e&&200!==e.status&&(e=void 0),e}}class C{constructor(t={}){this.cacheName=w(t.cacheName),this.plugins=t.plugins||[],this.fetchOptions=t.fetchOptions,this.matchOptions=t.matchOptions}handle(t){const[e]=this.handleAll(t);return e}handleAll(t){t instanceof FetchEvent&&(t={event:t,request:t.request});const e=t.event,s="string"==typeof t.request?new Request(t.request):t.request,n="params"in t?t.params:void 0,i=new b(this,{event:e,request:s,params:n}),r=this.q(i,s,e);return[r,this.U(r,i,s,e)]}async q(t,e,n){let i;await t.runCallbacks("handlerWillStart",{event:n,request:e});try{if(i=await this.L(e,t),!i||"error"===i.type)throw new s("no-response",{url:e.url})}catch(s){if(s instanceof Error)for(const r of t.iterateCallbacks("handlerDidError"))if(i=await r({error:s,event:n,request:e}),i)break;if(!i)throw s}for(const s of t.iterateCallbacks("handlerWillRespond"))i=await s({event:n,request:e,response:i});return i}async U(t,e,s,n){let i,r;try{i=await t}catch(r){}try{await e.runCallbacks("handlerDidRespond",{event:n,request:s,response:i}),await e.doneWaiting()}catch(t){t instanceof Error&&(r=t)}if(await e.runCallbacks("handlerDidComplete",{event:n,request:s,response:i,error:r}),e.destroy(),r)throw r}}class E extends C{constructor(t={}){t.cacheName=f(t.cacheName),super(t),this._=!1!==t.fallbackToNetwork,this.plugins.push(E.copyRedirectedCacheableResponsesPlugin)}async L(t,e){const s=await e.cacheMatch(t);return s||(e.event&&"install"===e.event.type?await this.C(t,e):await this.O(t,e))}async O(t,e){let n;const i=e.params||{};if(!this._)throw new s("missing-precache-entry",{cacheName:this.cacheName,url:t.url});{const s=i.integrity,r=t.integrity,o=!r||r===s;n=await e.fetch(new Request(t,{integrity:"no-cors"!==t.mode?r||s:void 0})),s&&o&&"no-cors"!==t.mode&&(this.N(),await e.cachePut(t,n.clone()))}return n}async C(t,e){this.N();const n=await e.fetch(t);if(!await e.cachePut(t,n.clone()))throw new s("bad-precaching-response",{url:t.url,status:n.status});return n}N(){let t=null,e=0;for(const[s,n]of this.plugins.entries())n!==E.copyRedirectedCacheableResponsesPlugin&&(n===E.defaultPrecacheCacheabilityPlugin&&(t=s),n.cacheWillUpdate&&e++);0===e?this.plugins.push(E.defaultPrecacheCacheabilityPlugin):e>1&&null!==t&&this.plugins.splice(t,1)}}E.defaultPrecacheCacheabilityPlugin={cacheWillUpdate:async({response:t})=>!t||t.status>=400?null:t},E.copyRedirectedCacheableResponsesPlugin={cacheWillUpdate:async({response:t})=>t.redirected?await m(t):t};class O{constructor({cacheName:t,plugins:e=[],fallbackToNetwork:s=!0}={}){this.k=new Map,this.K=new Map,this.P=new Map,this.l=new E({cacheName:f(t),plugins:[...e,new g({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this.l}precache(t){this.addToCacheList(t),this.T||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this.T=!0)}addToCacheList(t){const e=[];for(const n of t){"string"==typeof n?e.push(n):n&&void 0===n.revision&&e.push(n.url);const{cacheKey:t,url:i}=p(n),r="string"!=typeof n&&n.revision?"reload":"default";if(this.k.has(i)&&this.k.get(i)!==t)throw new s("add-to-cache-list-conflicting-entries",{firstEntry:this.k.get(i),secondEntry:t});if("string"!=typeof n&&n.integrity){if(this.P.has(t)&&this.P.get(t)!==n.integrity)throw new s("add-to-cache-list-conflicting-integrities",{url:i});this.P.set(t,n.integrity)}if(this.k.set(i,t),this.K.set(i,r),e.length>0){const t=`Workbox is precaching URLs without revision info: ${e.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(t)}}}install(t){return d(t,(async()=>{const e=new y;this.strategy.plugins.push(e);for(const[e,s]of this.k){const n=this.P.get(s),i=this.K.get(e),r=new Request(e,{integrity:n,cache:i,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:s},request:r,event:t}))}const{updatedURLs:s,notUpdatedURLs:n}=e;return{updatedURLs:s,notUpdatedURLs:n}}))}activate(t){return d(t,(async()=>{const t=await self.caches.open(this.strategy.cacheName),e=await t.keys(),s=new Set(this.k.values()),n=[];for(const i of e)s.has(i.url)||(await t.delete(i),n.push(i.url));return{deletedURLs:n}}))}getURLsToCacheKeys(){return this.k}getCachedURLs(){return[...this.k.keys()]}getCacheKeyForURL(t){const e=new URL(t,location.href);return this.k.get(e.href)}getIntegrityForCacheKey(t){return this.P.get(t)}async matchPrecache(t){const e=t instanceof Request?t.url:t,s=this.getCacheKeyForURL(e);if(s){return(await self.caches.open(this.strategy.cacheName)).match(s)}}createHandlerBoundToURL(t){const e=this.getCacheKeyForURL(t);if(!e)throw new s("non-precached-url",{url:t});return s=>(s.request=new Request(t),s.params=Object.assign({cacheKey:e},s.params),this.strategy.handle(s))}}let x;const N=()=>(x||(x=new O),x);class k extends i{constructor(t,e){super((({request:s})=>{const n=t.getURLsToCacheKeys();for(const i of function*(t,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:s="index.html",cleanURLs:n=!0,urlManipulation:i}={}){const r=new URL(t,location.href);r.hash="",yield r.href;const o=function(t,e=[]){for(const s of[...t.searchParams.keys()])e.some((t=>t.test(s)))&&t.searchParams.delete(s);return t}(r,e);if(yield o.href,s&&o.pathname.endsWith("/")){const t=new URL(o.href);t.pathname+=s,yield t.href}if(n){const t=new URL(o.href);t.pathname+=".html",yield t.href}if(i){const t=i({url:r});for(const e of t)yield e.href}}(s.url,e)){const e=n.get(i);if(e){return{cacheKey:e,integrity:t.getIntegrityForCacheKey(e)}}}}),t.strategy)}}t.NavigationRoute=class extends i{constructor(t,{allowlist:e=[/./],denylist:s=[]}={}){super((t=>this.W(t)),t),this.j=e,this.M=s}W({url:t,request:e}){if(e&&"navigate"!==e.mode)return!1;const s=t.pathname+t.search;for(const t of this.M)if(t.test(s))return!1;return!!this.j.some((t=>t.test(s)))}},t.cleanupOutdatedCaches=function(){self.addEventListener("activate",(t=>{const e=f();t.waitUntil((async(t,e="-precache-")=>{const s=(await self.caches.keys()).filter((s=>s.includes(e)&&s.includes(self.registration.scope)&&s!==t));return await Promise.all(s.map((t=>self.caches.delete(t)))),s})(e).then((t=>{})))}))},t.createHandlerBoundToURL=function(t){return N().createHandlerBoundToURL(t)},t.precacheAndRoute=function(t,e){!function(t){N().precache(t)}(t),function(t){const e=N();h(new k(e,t))}(e)},t.registerRoute=h}));
2 |
--------------------------------------------------------------------------------
/dev-dist/workbox-3625d7b0.js:
--------------------------------------------------------------------------------
1 | define(["exports"],(function(t){"use strict";try{self["workbox:core:6.5.3"]&&_()}catch(t){}const e=(t,...e)=>{let s=t;return e.length>0&&(s+=` :: ${JSON.stringify(e)}`),s};class s extends Error{constructor(t,s){super(e(t,s)),this.name=t,this.details=s}}try{self["workbox:routing:6.5.3"]&&_()}catch(t){}const n=t=>t&&"object"==typeof t?t:{handle:t};class i{constructor(t,e,s="GET"){this.handler=n(e),this.match=t,this.method=s}setCatchHandler(t){this.catchHandler=n(t)}}class r extends i{constructor(t,e,s){super((({url:e})=>{const s=t.exec(e.href);if(s&&(e.origin===location.origin||0===s.index))return s.slice(1)}),e,s)}}class o{constructor(){this.t=new Map,this.i=new Map}get routes(){return this.t}addFetchListener(){self.addEventListener("fetch",(t=>{const{request:e}=t,s=this.handleRequest({request:e,event:t});s&&t.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(t=>{if(t.data&&"CACHE_URLS"===t.data.type){const{payload:e}=t.data,s=Promise.all(e.urlsToCache.map((e=>{"string"==typeof e&&(e=[e]);const s=new Request(...e);return this.handleRequest({request:s,event:t})})));t.waitUntil(s),t.ports&&t.ports[0]&&s.then((()=>t.ports[0].postMessage(!0)))}}))}handleRequest({request:t,event:e}){const s=new URL(t.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:i,route:r}=this.findMatchingRoute({event:e,request:t,sameOrigin:n,url:s});let o=r&&r.handler;const c=t.method;if(!o&&this.i.has(c)&&(o=this.i.get(c)),!o)return;let a;try{a=o.handle({url:s,request:t,event:e,params:i})}catch(t){a=Promise.reject(t)}const h=r&&r.catchHandler;return a instanceof Promise&&(this.o||h)&&(a=a.catch((async n=>{if(h)try{return await h.handle({url:s,request:t,event:e,params:i})}catch(t){t instanceof Error&&(n=t)}if(this.o)return this.o.handle({url:s,request:t,event:e});throw n}))),a}findMatchingRoute({url:t,sameOrigin:e,request:s,event:n}){const i=this.t.get(s.method)||[];for(const r of i){let i;const o=r.match({url:t,sameOrigin:e,request:s,event:n});if(o)return i=o,(Array.isArray(i)&&0===i.length||o.constructor===Object&&0===Object.keys(o).length||"boolean"==typeof o)&&(i=void 0),{route:r,params:i}}return{}}setDefaultHandler(t,e="GET"){this.i.set(e,n(t))}setCatchHandler(t){this.o=n(t)}registerRoute(t){this.t.has(t.method)||this.t.set(t.method,[]),this.t.get(t.method).push(t)}unregisterRoute(t){if(!this.t.has(t.method))throw new s("unregister-route-but-not-found-with-method",{method:t.method});const e=this.t.get(t.method).indexOf(t);if(!(e>-1))throw new s("unregister-route-route-not-registered");this.t.get(t.method).splice(e,1)}}let c;const a=()=>(c||(c=new o,c.addFetchListener(),c.addCacheListener()),c);function h(t,e,n){let o;if("string"==typeof t){const s=new URL(t,location.href);o=new i((({url:t})=>t.href===s.href),e,n)}else if(t instanceof RegExp)o=new r(t,e,n);else if("function"==typeof t)o=new i(t,e,n);else{if(!(t instanceof i))throw new s("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});o=t}return a().registerRoute(o),o}const u={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},l=t=>[u.prefix,t,u.suffix].filter((t=>t&&t.length>0)).join("-"),f=t=>t||l(u.precache),w=t=>t||l(u.runtime);function d(t,e){const s=e();return t.waitUntil(s),s}try{self["workbox:precaching:6.5.3"]&&_()}catch(t){}function p(t){if(!t)throw new s("add-to-cache-list-unexpected-type",{entry:t});if("string"==typeof t){const e=new URL(t,location.href);return{cacheKey:e.href,url:e.href}}const{revision:e,url:n}=t;if(!n)throw new s("add-to-cache-list-unexpected-type",{entry:t});if(!e){const t=new URL(n,location.href);return{cacheKey:t.href,url:t.href}}const i=new URL(n,location.href),r=new URL(n,location.href);return i.searchParams.set("__WB_REVISION__",e),{cacheKey:i.href,url:r.href}}class y{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:t,state:e})=>{e&&(e.originalRequest=t)},this.cachedResponseWillBeUsed=async({event:t,state:e,cachedResponse:s})=>{if("install"===t.type&&e&&e.originalRequest&&e.originalRequest instanceof Request){const t=e.originalRequest.url;s?this.notUpdatedURLs.push(t):this.updatedURLs.push(t)}return s}}}class g{constructor({precacheController:t}){this.cacheKeyWillBeUsed=async({request:t,params:e})=>{const s=(null==e?void 0:e.cacheKey)||this.h.getCacheKeyForURL(t.url);return s?new Request(s,{headers:t.headers}):t},this.h=t}}let R;async function m(t,e){let n=null;if(t.url){n=new URL(t.url).origin}if(n!==self.location.origin)throw new s("cross-origin-copy-response",{origin:n});const i=t.clone(),r={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=e?e(r):r,c=function(){if(void 0===R){const t=new Response("");if("body"in t)try{new Response(t.body),R=!0}catch(t){R=!1}R=!1}return R}()?i.body:await i.blob();return new Response(c,o)}function v(t,e){const s=new URL(t);for(const t of e)s.searchParams.delete(t);return s.href}class q{constructor(){this.promise=new Promise(((t,e)=>{this.resolve=t,this.reject=e}))}}const U=new Set;try{self["workbox:strategies:6.5.3"]&&_()}catch(t){}function L(t){return"string"==typeof t?new Request(t):t}class b{constructor(t,e){this.u={},Object.assign(this,e),this.event=e.event,this.l=t,this.p=new q,this.g=[],this.R=[...t.plugins],this.m=new Map;for(const t of this.R)this.m.set(t,{});this.event.waitUntil(this.p.promise)}async fetch(t){const{event:e}=this;let n=L(t);if("navigate"===n.mode&&e instanceof FetchEvent&&e.preloadResponse){const t=await e.preloadResponse;if(t)return t}const i=this.hasCallback("fetchDidFail")?n.clone():null;try{for(const t of this.iterateCallbacks("requestWillFetch"))n=await t({request:n.clone(),event:e})}catch(t){if(t instanceof Error)throw new s("plugin-error-request-will-fetch",{thrownErrorMessage:t.message})}const r=n.clone();try{let t;t=await fetch(n,"navigate"===n.mode?void 0:this.l.fetchOptions);for(const s of this.iterateCallbacks("fetchDidSucceed"))t=await s({event:e,request:r,response:t});return t}catch(t){throw i&&await this.runCallbacks("fetchDidFail",{error:t,event:e,originalRequest:i.clone(),request:r.clone()}),t}}async fetchAndCachePut(t){const e=await this.fetch(t),s=e.clone();return this.waitUntil(this.cachePut(t,s)),e}async cacheMatch(t){const e=L(t);let s;const{cacheName:n,matchOptions:i}=this.l,r=await this.getCacheKey(e,"read"),o=Object.assign(Object.assign({},i),{cacheName:n});s=await caches.match(r,o);for(const t of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await t({cacheName:n,matchOptions:i,cachedResponse:s,request:r,event:this.event})||void 0;return s}async cachePut(t,e){const n=L(t);var i;await(i=0,new Promise((t=>setTimeout(t,i))));const r=await this.getCacheKey(n,"write");if(!e)throw new s("cache-put-with-no-response",{url:(o=r.url,new URL(String(o),location.href).href.replace(new RegExp(`^${location.origin}`),""))});var o;const c=await this.v(e);if(!c)return!1;const{cacheName:a,matchOptions:h}=this.l,u=await self.caches.open(a),l=this.hasCallback("cacheDidUpdate"),f=l?await async function(t,e,s,n){const i=v(e.url,s);if(e.url===i)return t.match(e,n);const r=Object.assign(Object.assign({},n),{ignoreSearch:!0}),o=await t.keys(e,r);for(const e of o)if(i===v(e.url,s))return t.match(e,n)}(u,r.clone(),["__WB_REVISION__"],h):null;try{await u.put(r,l?c.clone():c)}catch(t){if(t instanceof Error)throw"QuotaExceededError"===t.name&&await async function(){for(const t of U)await t()}(),t}for(const t of this.iterateCallbacks("cacheDidUpdate"))await t({cacheName:a,oldResponse:f,newResponse:c.clone(),request:r,event:this.event});return!0}async getCacheKey(t,e){const s=`${t.url} | ${e}`;if(!this.u[s]){let n=t;for(const t of this.iterateCallbacks("cacheKeyWillBeUsed"))n=L(await t({mode:e,request:n,event:this.event,params:this.params}));this.u[s]=n}return this.u[s]}hasCallback(t){for(const e of this.l.plugins)if(t in e)return!0;return!1}async runCallbacks(t,e){for(const s of this.iterateCallbacks(t))await s(e)}*iterateCallbacks(t){for(const e of this.l.plugins)if("function"==typeof e[t]){const s=this.m.get(e),n=n=>{const i=Object.assign(Object.assign({},n),{state:s});return e[t](i)};yield n}}waitUntil(t){return this.g.push(t),t}async doneWaiting(){let t;for(;t=this.g.shift();)await t}destroy(){this.p.resolve(null)}async v(t){let e=t,s=!1;for(const t of this.iterateCallbacks("cacheWillUpdate"))if(e=await t({request:this.request,response:e,event:this.event})||void 0,s=!0,!e)break;return s||e&&200!==e.status&&(e=void 0),e}}class C{constructor(t={}){this.cacheName=w(t.cacheName),this.plugins=t.plugins||[],this.fetchOptions=t.fetchOptions,this.matchOptions=t.matchOptions}handle(t){const[e]=this.handleAll(t);return e}handleAll(t){t instanceof FetchEvent&&(t={event:t,request:t.request});const e=t.event,s="string"==typeof t.request?new Request(t.request):t.request,n="params"in t?t.params:void 0,i=new b(this,{event:e,request:s,params:n}),r=this.q(i,s,e);return[r,this.U(r,i,s,e)]}async q(t,e,n){let i;await t.runCallbacks("handlerWillStart",{event:n,request:e});try{if(i=await this.L(e,t),!i||"error"===i.type)throw new s("no-response",{url:e.url})}catch(s){if(s instanceof Error)for(const r of t.iterateCallbacks("handlerDidError"))if(i=await r({error:s,event:n,request:e}),i)break;if(!i)throw s}for(const s of t.iterateCallbacks("handlerWillRespond"))i=await s({event:n,request:e,response:i});return i}async U(t,e,s,n){let i,r;try{i=await t}catch(r){}try{await e.runCallbacks("handlerDidRespond",{event:n,request:s,response:i}),await e.doneWaiting()}catch(t){t instanceof Error&&(r=t)}if(await e.runCallbacks("handlerDidComplete",{event:n,request:s,response:i,error:r}),e.destroy(),r)throw r}}class E extends C{constructor(t={}){t.cacheName=f(t.cacheName),super(t),this._=!1!==t.fallbackToNetwork,this.plugins.push(E.copyRedirectedCacheableResponsesPlugin)}async L(t,e){const s=await e.cacheMatch(t);return s||(e.event&&"install"===e.event.type?await this.C(t,e):await this.O(t,e))}async O(t,e){let n;const i=e.params||{};if(!this._)throw new s("missing-precache-entry",{cacheName:this.cacheName,url:t.url});{const s=i.integrity,r=t.integrity,o=!r||r===s;n=await e.fetch(new Request(t,{integrity:"no-cors"!==t.mode?r||s:void 0})),s&&o&&"no-cors"!==t.mode&&(this.N(),await e.cachePut(t,n.clone()))}return n}async C(t,e){this.N();const n=await e.fetch(t);if(!await e.cachePut(t,n.clone()))throw new s("bad-precaching-response",{url:t.url,status:n.status});return n}N(){let t=null,e=0;for(const[s,n]of this.plugins.entries())n!==E.copyRedirectedCacheableResponsesPlugin&&(n===E.defaultPrecacheCacheabilityPlugin&&(t=s),n.cacheWillUpdate&&e++);0===e?this.plugins.push(E.defaultPrecacheCacheabilityPlugin):e>1&&null!==t&&this.plugins.splice(t,1)}}E.defaultPrecacheCacheabilityPlugin={cacheWillUpdate:async({response:t})=>!t||t.status>=400?null:t},E.copyRedirectedCacheableResponsesPlugin={cacheWillUpdate:async({response:t})=>t.redirected?await m(t):t};class O{constructor({cacheName:t,plugins:e=[],fallbackToNetwork:s=!0}={}){this.k=new Map,this.K=new Map,this.P=new Map,this.l=new E({cacheName:f(t),plugins:[...e,new g({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this.l}precache(t){this.addToCacheList(t),this.T||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this.T=!0)}addToCacheList(t){const e=[];for(const n of t){"string"==typeof n?e.push(n):n&&void 0===n.revision&&e.push(n.url);const{cacheKey:t,url:i}=p(n),r="string"!=typeof n&&n.revision?"reload":"default";if(this.k.has(i)&&this.k.get(i)!==t)throw new s("add-to-cache-list-conflicting-entries",{firstEntry:this.k.get(i),secondEntry:t});if("string"!=typeof n&&n.integrity){if(this.P.has(t)&&this.P.get(t)!==n.integrity)throw new s("add-to-cache-list-conflicting-integrities",{url:i});this.P.set(t,n.integrity)}if(this.k.set(i,t),this.K.set(i,r),e.length>0){const t=`Workbox is precaching URLs without revision info: ${e.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(t)}}}install(t){return d(t,(async()=>{const e=new y;this.strategy.plugins.push(e);for(const[e,s]of this.k){const n=this.P.get(s),i=this.K.get(e),r=new Request(e,{integrity:n,cache:i,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:s},request:r,event:t}))}const{updatedURLs:s,notUpdatedURLs:n}=e;return{updatedURLs:s,notUpdatedURLs:n}}))}activate(t){return d(t,(async()=>{const t=await self.caches.open(this.strategy.cacheName),e=await t.keys(),s=new Set(this.k.values()),n=[];for(const i of e)s.has(i.url)||(await t.delete(i),n.push(i.url));return{deletedURLs:n}}))}getURLsToCacheKeys(){return this.k}getCachedURLs(){return[...this.k.keys()]}getCacheKeyForURL(t){const e=new URL(t,location.href);return this.k.get(e.href)}getIntegrityForCacheKey(t){return this.P.get(t)}async matchPrecache(t){const e=t instanceof Request?t.url:t,s=this.getCacheKeyForURL(e);if(s){return(await self.caches.open(this.strategy.cacheName)).match(s)}}createHandlerBoundToURL(t){const e=this.getCacheKeyForURL(t);if(!e)throw new s("non-precached-url",{url:t});return s=>(s.request=new Request(t),s.params=Object.assign({cacheKey:e},s.params),this.strategy.handle(s))}}let x;const N=()=>(x||(x=new O),x);class k extends i{constructor(t,e){super((({request:s})=>{const n=t.getURLsToCacheKeys();for(const i of function*(t,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:s="index.html",cleanURLs:n=!0,urlManipulation:i}={}){const r=new URL(t,location.href);r.hash="",yield r.href;const o=function(t,e=[]){for(const s of[...t.searchParams.keys()])e.some((t=>t.test(s)))&&t.searchParams.delete(s);return t}(r,e);if(yield o.href,s&&o.pathname.endsWith("/")){const t=new URL(o.href);t.pathname+=s,yield t.href}if(n){const t=new URL(o.href);t.pathname+=".html",yield t.href}if(i){const t=i({url:r});for(const e of t)yield e.href}}(s.url,e)){const e=n.get(i);if(e){return{cacheKey:e,integrity:t.getIntegrityForCacheKey(e)}}}}),t.strategy)}}t.NavigationRoute=class extends i{constructor(t,{allowlist:e=[/./],denylist:s=[]}={}){super((t=>this.W(t)),t),this.j=e,this.M=s}W({url:t,request:e}){if(e&&"navigate"!==e.mode)return!1;const s=t.pathname+t.search;for(const t of this.M)if(t.test(s))return!1;return!!this.j.some((t=>t.test(s)))}},t.cleanupOutdatedCaches=function(){self.addEventListener("activate",(t=>{const e=f();t.waitUntil((async(t,e="-precache-")=>{const s=(await self.caches.keys()).filter((s=>s.includes(e)&&s.includes(self.registration.scope)&&s!==t));return await Promise.all(s.map((t=>self.caches.delete(t)))),s})(e).then((t=>{})))}))},t.clientsClaim=function(){self.addEventListener("activate",(()=>self.clients.claim()))},t.createHandlerBoundToURL=function(t){return N().createHandlerBoundToURL(t)},t.precacheAndRoute=function(t,e){!function(t){N().precache(t)}(t),function(t){const e=N();h(new k(e,t))}(e)},t.registerRoute=h}));
2 |
--------------------------------------------------------------------------------
/dev-dist/workbox-519d0965.js:
--------------------------------------------------------------------------------
1 | define(["exports"],(function(t){"use strict";try{self["workbox:core:6.5.3"]&&_()}catch(t){}const e=(t,...e)=>{let s=t;return e.length>0&&(s+=` :: ${JSON.stringify(e)}`),s};class s extends Error{constructor(t,s){super(e(t,s)),this.name=t,this.details=s}}try{self["workbox:routing:6.5.3"]&&_()}catch(t){}const n=t=>t&&"object"==typeof t?t:{handle:t};class r{constructor(t,e,s="GET"){this.handler=n(e),this.match=t,this.method=s}setCatchHandler(t){this.catchHandler=n(t)}}class i extends r{constructor(t,e,s){super((({url:e})=>{const s=t.exec(e.href);if(s&&(e.origin===location.origin||0===s.index))return s.slice(1)}),e,s)}}class o{constructor(){this.t=new Map,this.i=new Map}get routes(){return this.t}addFetchListener(){self.addEventListener("fetch",(t=>{const{request:e}=t,s=this.handleRequest({request:e,event:t});s&&t.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(t=>{if(t.data&&"CACHE_URLS"===t.data.type){const{payload:e}=t.data,s=Promise.all(e.urlsToCache.map((e=>{"string"==typeof e&&(e=[e]);const s=new Request(...e);return this.handleRequest({request:s,event:t})})));t.waitUntil(s),t.ports&&t.ports[0]&&s.then((()=>t.ports[0].postMessage(!0)))}}))}handleRequest({request:t,event:e}){const s=new URL(t.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:r,route:i}=this.findMatchingRoute({event:e,request:t,sameOrigin:n,url:s});let o=i&&i.handler;const c=t.method;if(!o&&this.i.has(c)&&(o=this.i.get(c)),!o)return;let a;try{a=o.handle({url:s,request:t,event:e,params:r})}catch(t){a=Promise.reject(t)}const h=i&&i.catchHandler;return a instanceof Promise&&(this.o||h)&&(a=a.catch((async n=>{if(h)try{return await h.handle({url:s,request:t,event:e,params:r})}catch(t){t instanceof Error&&(n=t)}if(this.o)return this.o.handle({url:s,request:t,event:e});throw n}))),a}findMatchingRoute({url:t,sameOrigin:e,request:s,event:n}){const r=this.t.get(s.method)||[];for(const i of r){let r;const o=i.match({url:t,sameOrigin:e,request:s,event:n});if(o)return r=o,(Array.isArray(r)&&0===r.length||o.constructor===Object&&0===Object.keys(o).length||"boolean"==typeof o)&&(r=void 0),{route:i,params:r}}return{}}setDefaultHandler(t,e="GET"){this.i.set(e,n(t))}setCatchHandler(t){this.o=n(t)}registerRoute(t){this.t.has(t.method)||this.t.set(t.method,[]),this.t.get(t.method).push(t)}unregisterRoute(t){if(!this.t.has(t.method))throw new s("unregister-route-but-not-found-with-method",{method:t.method});const e=this.t.get(t.method).indexOf(t);if(!(e>-1))throw new s("unregister-route-route-not-registered");this.t.get(t.method).splice(e,1)}}let c;const a=()=>(c||(c=new o,c.addFetchListener(),c.addCacheListener()),c);function h(t,e,n){let o;if("string"==typeof t){const s=new URL(t,location.href);o=new r((({url:t})=>t.href===s.href),e,n)}else if(t instanceof RegExp)o=new i(t,e,n);else if("function"==typeof t)o=new r(t,e,n);else{if(!(t instanceof r))throw new s("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});o=t}return a().registerRoute(o),o}try{self["workbox:cacheable-response:6.5.3"]&&_()}catch(t){}class u{constructor(t={}){this.h=t.statuses,this.u=t.headers}isResponseCacheable(t){let e=!0;return this.h&&(e=this.h.includes(t.status)),this.u&&e&&(e=Object.keys(this.u).some((e=>t.headers.get(e)===this.u[e]))),e}}const l={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},f=t=>[l.prefix,t,l.suffix].filter((t=>t&&t.length>0)).join("-"),w=t=>t||f(l.precache),d=t=>t||f(l.runtime);function p(t,e){const s=new URL(t);for(const t of e)s.searchParams.delete(t);return s.href}class y{constructor(){this.promise=new Promise(((t,e)=>{this.resolve=t,this.reject=e}))}}const g=new Set;try{self["workbox:strategies:6.5.3"]&&_()}catch(t){}function R(t){return"string"==typeof t?new Request(t):t}class m{constructor(t,e){this.l={},Object.assign(this,e),this.event=e.event,this.p=t,this.g=new y,this.R=[],this.m=[...t.plugins],this.v=new Map;for(const t of this.m)this.v.set(t,{});this.event.waitUntil(this.g.promise)}async fetch(t){const{event:e}=this;let n=R(t);if("navigate"===n.mode&&e instanceof FetchEvent&&e.preloadResponse){const t=await e.preloadResponse;if(t)return t}const r=this.hasCallback("fetchDidFail")?n.clone():null;try{for(const t of this.iterateCallbacks("requestWillFetch"))n=await t({request:n.clone(),event:e})}catch(t){if(t instanceof Error)throw new s("plugin-error-request-will-fetch",{thrownErrorMessage:t.message})}const i=n.clone();try{let t;t=await fetch(n,"navigate"===n.mode?void 0:this.p.fetchOptions);for(const s of this.iterateCallbacks("fetchDidSucceed"))t=await s({event:e,request:i,response:t});return t}catch(t){throw r&&await this.runCallbacks("fetchDidFail",{error:t,event:e,originalRequest:r.clone(),request:i.clone()}),t}}async fetchAndCachePut(t){const e=await this.fetch(t),s=e.clone();return this.waitUntil(this.cachePut(t,s)),e}async cacheMatch(t){const e=R(t);let s;const{cacheName:n,matchOptions:r}=this.p,i=await this.getCacheKey(e,"read"),o=Object.assign(Object.assign({},r),{cacheName:n});s=await caches.match(i,o);for(const t of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await t({cacheName:n,matchOptions:r,cachedResponse:s,request:i,event:this.event})||void 0;return s}async cachePut(t,e){const n=R(t);var r;await(r=0,new Promise((t=>setTimeout(t,r))));const i=await this.getCacheKey(n,"write");if(!e)throw new s("cache-put-with-no-response",{url:(o=i.url,new URL(String(o),location.href).href.replace(new RegExp(`^${location.origin}`),""))});var o;const c=await this.q(e);if(!c)return!1;const{cacheName:a,matchOptions:h}=this.p,u=await self.caches.open(a),l=this.hasCallback("cacheDidUpdate"),f=l?await async function(t,e,s,n){const r=p(e.url,s);if(e.url===r)return t.match(e,n);const i=Object.assign(Object.assign({},n),{ignoreSearch:!0}),o=await t.keys(e,i);for(const e of o)if(r===p(e.url,s))return t.match(e,n)}(u,i.clone(),["__WB_REVISION__"],h):null;try{await u.put(i,l?c.clone():c)}catch(t){if(t instanceof Error)throw"QuotaExceededError"===t.name&&await async function(){for(const t of g)await t()}(),t}for(const t of this.iterateCallbacks("cacheDidUpdate"))await t({cacheName:a,oldResponse:f,newResponse:c.clone(),request:i,event:this.event});return!0}async getCacheKey(t,e){const s=`${t.url} | ${e}`;if(!this.l[s]){let n=t;for(const t of this.iterateCallbacks("cacheKeyWillBeUsed"))n=R(await t({mode:e,request:n,event:this.event,params:this.params}));this.l[s]=n}return this.l[s]}hasCallback(t){for(const e of this.p.plugins)if(t in e)return!0;return!1}async runCallbacks(t,e){for(const s of this.iterateCallbacks(t))await s(e)}*iterateCallbacks(t){for(const e of this.p.plugins)if("function"==typeof e[t]){const s=this.v.get(e),n=n=>{const r=Object.assign(Object.assign({},n),{state:s});return e[t](r)};yield n}}waitUntil(t){return this.R.push(t),t}async doneWaiting(){let t;for(;t=this.R.shift();)await t}destroy(){this.g.resolve(null)}async q(t){let e=t,s=!1;for(const t of this.iterateCallbacks("cacheWillUpdate"))if(e=await t({request:this.request,response:e,event:this.event})||void 0,s=!0,!e)break;return s||e&&200!==e.status&&(e=void 0),e}}class v{constructor(t={}){this.cacheName=d(t.cacheName),this.plugins=t.plugins||[],this.fetchOptions=t.fetchOptions,this.matchOptions=t.matchOptions}handle(t){const[e]=this.handleAll(t);return e}handleAll(t){t instanceof FetchEvent&&(t={event:t,request:t.request});const e=t.event,s="string"==typeof t.request?new Request(t.request):t.request,n="params"in t?t.params:void 0,r=new m(this,{event:e,request:s,params:n}),i=this.U(r,s,e);return[i,this.L(i,r,s,e)]}async U(t,e,n){let r;await t.runCallbacks("handlerWillStart",{event:n,request:e});try{if(r=await this._(e,t),!r||"error"===r.type)throw new s("no-response",{url:e.url})}catch(s){if(s instanceof Error)for(const i of t.iterateCallbacks("handlerDidError"))if(r=await i({error:s,event:n,request:e}),r)break;if(!r)throw s}for(const s of t.iterateCallbacks("handlerWillRespond"))r=await s({event:n,request:e,response:r});return r}async L(t,e,s,n){let r,i;try{r=await t}catch(i){}try{await e.runCallbacks("handlerDidRespond",{event:n,request:s,response:r}),await e.doneWaiting()}catch(t){t instanceof Error&&(i=t)}if(await e.runCallbacks("handlerDidComplete",{event:n,request:s,response:r,error:i}),e.destroy(),i)throw i}}function q(t,e){const s=e();return t.waitUntil(s),s}try{self["workbox:precaching:6.5.3"]&&_()}catch(t){}function U(t){if(!t)throw new s("add-to-cache-list-unexpected-type",{entry:t});if("string"==typeof t){const e=new URL(t,location.href);return{cacheKey:e.href,url:e.href}}const{revision:e,url:n}=t;if(!n)throw new s("add-to-cache-list-unexpected-type",{entry:t});if(!e){const t=new URL(n,location.href);return{cacheKey:t.href,url:t.href}}const r=new URL(n,location.href),i=new URL(n,location.href);return r.searchParams.set("__WB_REVISION__",e),{cacheKey:r.href,url:i.href}}class b{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:t,state:e})=>{e&&(e.originalRequest=t)},this.cachedResponseWillBeUsed=async({event:t,state:e,cachedResponse:s})=>{if("install"===t.type&&e&&e.originalRequest&&e.originalRequest instanceof Request){const t=e.originalRequest.url;s?this.notUpdatedURLs.push(t):this.updatedURLs.push(t)}return s}}}class L{constructor({precacheController:t}){this.cacheKeyWillBeUsed=async({request:t,params:e})=>{const s=(null==e?void 0:e.cacheKey)||this.C.getCacheKeyForURL(t.url);return s?new Request(s,{headers:t.headers}):t},this.C=t}}let C,E;async function O(t,e){let n=null;if(t.url){n=new URL(t.url).origin}if(n!==self.location.origin)throw new s("cross-origin-copy-response",{origin:n});const r=t.clone(),i={headers:new Headers(r.headers),status:r.status,statusText:r.statusText},o=e?e(i):i,c=function(){if(void 0===C){const t=new Response("");if("body"in t)try{new Response(t.body),C=!0}catch(t){C=!1}C=!1}return C}()?r.body:await r.blob();return new Response(c,o)}class x extends v{constructor(t={}){t.cacheName=w(t.cacheName),super(t),this.O=!1!==t.fallbackToNetwork,this.plugins.push(x.copyRedirectedCacheableResponsesPlugin)}async _(t,e){const s=await e.cacheMatch(t);return s||(e.event&&"install"===e.event.type?await this.N(t,e):await this.j(t,e))}async j(t,e){let n;const r=e.params||{};if(!this.O)throw new s("missing-precache-entry",{cacheName:this.cacheName,url:t.url});{const s=r.integrity,i=t.integrity,o=!i||i===s;n=await e.fetch(new Request(t,{integrity:"no-cors"!==t.mode?i||s:void 0})),s&&o&&"no-cors"!==t.mode&&(this.k(),await e.cachePut(t,n.clone()))}return n}async N(t,e){this.k();const n=await e.fetch(t);if(!await e.cachePut(t,n.clone()))throw new s("bad-precaching-response",{url:t.url,status:n.status});return n}k(){let t=null,e=0;for(const[s,n]of this.plugins.entries())n!==x.copyRedirectedCacheableResponsesPlugin&&(n===x.defaultPrecacheCacheabilityPlugin&&(t=s),n.cacheWillUpdate&&e++);0===e?this.plugins.push(x.defaultPrecacheCacheabilityPlugin):e>1&&null!==t&&this.plugins.splice(t,1)}}x.defaultPrecacheCacheabilityPlugin={cacheWillUpdate:async({response:t})=>!t||t.status>=400?null:t},x.copyRedirectedCacheableResponsesPlugin={cacheWillUpdate:async({response:t})=>t.redirected?await O(t):t};class N{constructor({cacheName:t,plugins:e=[],fallbackToNetwork:s=!0}={}){this.K=new Map,this.P=new Map,this.T=new Map,this.p=new x({cacheName:w(t),plugins:[...e,new L({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this.p}precache(t){this.addToCacheList(t),this.W||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this.W=!0)}addToCacheList(t){const e=[];for(const n of t){"string"==typeof n?e.push(n):n&&void 0===n.revision&&e.push(n.url);const{cacheKey:t,url:r}=U(n),i="string"!=typeof n&&n.revision?"reload":"default";if(this.K.has(r)&&this.K.get(r)!==t)throw new s("add-to-cache-list-conflicting-entries",{firstEntry:this.K.get(r),secondEntry:t});if("string"!=typeof n&&n.integrity){if(this.T.has(t)&&this.T.get(t)!==n.integrity)throw new s("add-to-cache-list-conflicting-integrities",{url:r});this.T.set(t,n.integrity)}if(this.K.set(r,t),this.P.set(r,i),e.length>0){const t=`Workbox is precaching URLs without revision info: ${e.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(t)}}}install(t){return q(t,(async()=>{const e=new b;this.strategy.plugins.push(e);for(const[e,s]of this.K){const n=this.T.get(s),r=this.P.get(e),i=new Request(e,{integrity:n,cache:r,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:s},request:i,event:t}))}const{updatedURLs:s,notUpdatedURLs:n}=e;return{updatedURLs:s,notUpdatedURLs:n}}))}activate(t){return q(t,(async()=>{const t=await self.caches.open(this.strategy.cacheName),e=await t.keys(),s=new Set(this.K.values()),n=[];for(const r of e)s.has(r.url)||(await t.delete(r),n.push(r.url));return{deletedURLs:n}}))}getURLsToCacheKeys(){return this.K}getCachedURLs(){return[...this.K.keys()]}getCacheKeyForURL(t){const e=new URL(t,location.href);return this.K.get(e.href)}getIntegrityForCacheKey(t){return this.T.get(t)}async matchPrecache(t){const e=t instanceof Request?t.url:t,s=this.getCacheKeyForURL(e);if(s){return(await self.caches.open(this.strategy.cacheName)).match(s)}}createHandlerBoundToURL(t){const e=this.getCacheKeyForURL(t);if(!e)throw new s("non-precached-url",{url:t});return s=>(s.request=new Request(t),s.params=Object.assign({cacheKey:e},s.params),this.strategy.handle(s))}}const j=()=>(E||(E=new N),E);class k extends r{constructor(t,e){super((({request:s})=>{const n=t.getURLsToCacheKeys();for(const r of function*(t,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:s="index.html",cleanURLs:n=!0,urlManipulation:r}={}){const i=new URL(t,location.href);i.hash="",yield i.href;const o=function(t,e=[]){for(const s of[...t.searchParams.keys()])e.some((t=>t.test(s)))&&t.searchParams.delete(s);return t}(i,e);if(yield o.href,s&&o.pathname.endsWith("/")){const t=new URL(o.href);t.pathname+=s,yield t.href}if(n){const t=new URL(o.href);t.pathname+=".html",yield t.href}if(r){const t=r({url:i});for(const e of t)yield e.href}}(s.url,e)){const e=n.get(r);if(e){return{cacheKey:e,integrity:t.getIntegrityForCacheKey(e)}}}}),t.strategy)}}t.CacheFirst=class extends v{async _(t,e){let n,r=await e.cacheMatch(t);if(!r)try{r=await e.fetchAndCachePut(t)}catch(t){t instanceof Error&&(n=t)}if(!r)throw new s("no-response",{url:t.url,error:n});return r}},t.CacheableResponsePlugin=class{constructor(t){this.cacheWillUpdate=async({response:t})=>this.M.isResponseCacheable(t)?t:null,this.M=new u(t)}},t.NavigationRoute=class extends r{constructor(t,{allowlist:e=[/./],denylist:s=[]}={}){super((t=>this.S(t)),t),this.D=e,this.F=s}S({url:t,request:e}){if(e&&"navigate"!==e.mode)return!1;const s=t.pathname+t.search;for(const t of this.F)if(t.test(s))return!1;return!!this.D.some((t=>t.test(s)))}},t.cleanupOutdatedCaches=function(){self.addEventListener("activate",(t=>{const e=w();t.waitUntil((async(t,e="-precache-")=>{const s=(await self.caches.keys()).filter((s=>s.includes(e)&&s.includes(self.registration.scope)&&s!==t));return await Promise.all(s.map((t=>self.caches.delete(t)))),s})(e).then((t=>{})))}))},t.createHandlerBoundToURL=function(t){return j().createHandlerBoundToURL(t)},t.precacheAndRoute=function(t,e){!function(t){j().precache(t)}(t),function(t){const e=j();h(new k(e,t))}(e)},t.registerRoute=h}));
2 |
--------------------------------------------------------------------------------
/dev-dist/workbox-68741b2f.js:
--------------------------------------------------------------------------------
1 | define(["exports"],(function(t){"use strict";try{self["workbox:core:6.5.3"]&&_()}catch(t){}const e=(t,...e)=>{let s=t;return e.length>0&&(s+=` :: ${JSON.stringify(e)}`),s};class s extends Error{constructor(t,s){super(e(t,s)),this.name=t,this.details=s}}try{self["workbox:routing:6.5.3"]&&_()}catch(t){}const n=t=>t&&"object"==typeof t?t:{handle:t};class r{constructor(t,e,s="GET"){this.handler=n(e),this.match=t,this.method=s}setCatchHandler(t){this.catchHandler=n(t)}}class i extends r{constructor(t,e,s){super((({url:e})=>{const s=t.exec(e.href);if(s&&(e.origin===location.origin||0===s.index))return s.slice(1)}),e,s)}}class o{constructor(){this.t=new Map,this.i=new Map}get routes(){return this.t}addFetchListener(){self.addEventListener("fetch",(t=>{const{request:e}=t,s=this.handleRequest({request:e,event:t});s&&t.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(t=>{if(t.data&&"CACHE_URLS"===t.data.type){const{payload:e}=t.data,s=Promise.all(e.urlsToCache.map((e=>{"string"==typeof e&&(e=[e]);const s=new Request(...e);return this.handleRequest({request:s,event:t})})));t.waitUntil(s),t.ports&&t.ports[0]&&s.then((()=>t.ports[0].postMessage(!0)))}}))}handleRequest({request:t,event:e}){const s=new URL(t.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:r,route:i}=this.findMatchingRoute({event:e,request:t,sameOrigin:n,url:s});let o=i&&i.handler;const c=t.method;if(!o&&this.i.has(c)&&(o=this.i.get(c)),!o)return;let a;try{a=o.handle({url:s,request:t,event:e,params:r})}catch(t){a=Promise.reject(t)}const h=i&&i.catchHandler;return a instanceof Promise&&(this.o||h)&&(a=a.catch((async n=>{if(h)try{return await h.handle({url:s,request:t,event:e,params:r})}catch(t){t instanceof Error&&(n=t)}if(this.o)return this.o.handle({url:s,request:t,event:e});throw n}))),a}findMatchingRoute({url:t,sameOrigin:e,request:s,event:n}){const r=this.t.get(s.method)||[];for(const i of r){let r;const o=i.match({url:t,sameOrigin:e,request:s,event:n});if(o)return r=o,(Array.isArray(r)&&0===r.length||o.constructor===Object&&0===Object.keys(o).length||"boolean"==typeof o)&&(r=void 0),{route:i,params:r}}return{}}setDefaultHandler(t,e="GET"){this.i.set(e,n(t))}setCatchHandler(t){this.o=n(t)}registerRoute(t){this.t.has(t.method)||this.t.set(t.method,[]),this.t.get(t.method).push(t)}unregisterRoute(t){if(!this.t.has(t.method))throw new s("unregister-route-but-not-found-with-method",{method:t.method});const e=this.t.get(t.method).indexOf(t);if(!(e>-1))throw new s("unregister-route-route-not-registered");this.t.get(t.method).splice(e,1)}}let c;const a=()=>(c||(c=new o,c.addFetchListener(),c.addCacheListener()),c);function h(t,e,n){let o;if("string"==typeof t){const s=new URL(t,location.href);o=new r((({url:t})=>t.href===s.href),e,n)}else if(t instanceof RegExp)o=new i(t,e,n);else if("function"==typeof t)o=new r(t,e,n);else{if(!(t instanceof r))throw new s("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});o=t}return a().registerRoute(o),o}try{self["workbox:cacheable-response:6.5.3"]&&_()}catch(t){}class u{constructor(t={}){this.h=t.statuses,this.u=t.headers}isResponseCacheable(t){let e=!0;return this.h&&(e=this.h.includes(t.status)),this.u&&e&&(e=Object.keys(this.u).some((e=>t.headers.get(e)===this.u[e]))),e}}const l={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},f=t=>[l.prefix,t,l.suffix].filter((t=>t&&t.length>0)).join("-"),w=t=>t||f(l.precache),d=t=>t||f(l.runtime);function p(t,e){const s=new URL(t);for(const t of e)s.searchParams.delete(t);return s.href}class y{constructor(){this.promise=new Promise(((t,e)=>{this.resolve=t,this.reject=e}))}}const g=new Set;try{self["workbox:strategies:6.5.3"]&&_()}catch(t){}function R(t){return"string"==typeof t?new Request(t):t}class m{constructor(t,e){this.l={},Object.assign(this,e),this.event=e.event,this.p=t,this.g=new y,this.R=[],this.m=[...t.plugins],this.v=new Map;for(const t of this.m)this.v.set(t,{});this.event.waitUntil(this.g.promise)}async fetch(t){const{event:e}=this;let n=R(t);if("navigate"===n.mode&&e instanceof FetchEvent&&e.preloadResponse){const t=await e.preloadResponse;if(t)return t}const r=this.hasCallback("fetchDidFail")?n.clone():null;try{for(const t of this.iterateCallbacks("requestWillFetch"))n=await t({request:n.clone(),event:e})}catch(t){if(t instanceof Error)throw new s("plugin-error-request-will-fetch",{thrownErrorMessage:t.message})}const i=n.clone();try{let t;t=await fetch(n,"navigate"===n.mode?void 0:this.p.fetchOptions);for(const s of this.iterateCallbacks("fetchDidSucceed"))t=await s({event:e,request:i,response:t});return t}catch(t){throw r&&await this.runCallbacks("fetchDidFail",{error:t,event:e,originalRequest:r.clone(),request:i.clone()}),t}}async fetchAndCachePut(t){const e=await this.fetch(t),s=e.clone();return this.waitUntil(this.cachePut(t,s)),e}async cacheMatch(t){const e=R(t);let s;const{cacheName:n,matchOptions:r}=this.p,i=await this.getCacheKey(e,"read"),o=Object.assign(Object.assign({},r),{cacheName:n});s=await caches.match(i,o);for(const t of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await t({cacheName:n,matchOptions:r,cachedResponse:s,request:i,event:this.event})||void 0;return s}async cachePut(t,e){const n=R(t);var r;await(r=0,new Promise((t=>setTimeout(t,r))));const i=await this.getCacheKey(n,"write");if(!e)throw new s("cache-put-with-no-response",{url:(o=i.url,new URL(String(o),location.href).href.replace(new RegExp(`^${location.origin}`),""))});var o;const c=await this.q(e);if(!c)return!1;const{cacheName:a,matchOptions:h}=this.p,u=await self.caches.open(a),l=this.hasCallback("cacheDidUpdate"),f=l?await async function(t,e,s,n){const r=p(e.url,s);if(e.url===r)return t.match(e,n);const i=Object.assign(Object.assign({},n),{ignoreSearch:!0}),o=await t.keys(e,i);for(const e of o)if(r===p(e.url,s))return t.match(e,n)}(u,i.clone(),["__WB_REVISION__"],h):null;try{await u.put(i,l?c.clone():c)}catch(t){if(t instanceof Error)throw"QuotaExceededError"===t.name&&await async function(){for(const t of g)await t()}(),t}for(const t of this.iterateCallbacks("cacheDidUpdate"))await t({cacheName:a,oldResponse:f,newResponse:c.clone(),request:i,event:this.event});return!0}async getCacheKey(t,e){const s=`${t.url} | ${e}`;if(!this.l[s]){let n=t;for(const t of this.iterateCallbacks("cacheKeyWillBeUsed"))n=R(await t({mode:e,request:n,event:this.event,params:this.params}));this.l[s]=n}return this.l[s]}hasCallback(t){for(const e of this.p.plugins)if(t in e)return!0;return!1}async runCallbacks(t,e){for(const s of this.iterateCallbacks(t))await s(e)}*iterateCallbacks(t){for(const e of this.p.plugins)if("function"==typeof e[t]){const s=this.v.get(e),n=n=>{const r=Object.assign(Object.assign({},n),{state:s});return e[t](r)};yield n}}waitUntil(t){return this.R.push(t),t}async doneWaiting(){let t;for(;t=this.R.shift();)await t}destroy(){this.g.resolve(null)}async q(t){let e=t,s=!1;for(const t of this.iterateCallbacks("cacheWillUpdate"))if(e=await t({request:this.request,response:e,event:this.event})||void 0,s=!0,!e)break;return s||e&&200!==e.status&&(e=void 0),e}}class v{constructor(t={}){this.cacheName=d(t.cacheName),this.plugins=t.plugins||[],this.fetchOptions=t.fetchOptions,this.matchOptions=t.matchOptions}handle(t){const[e]=this.handleAll(t);return e}handleAll(t){t instanceof FetchEvent&&(t={event:t,request:t.request});const e=t.event,s="string"==typeof t.request?new Request(t.request):t.request,n="params"in t?t.params:void 0,r=new m(this,{event:e,request:s,params:n}),i=this.U(r,s,e);return[i,this.L(i,r,s,e)]}async U(t,e,n){let r;await t.runCallbacks("handlerWillStart",{event:n,request:e});try{if(r=await this._(e,t),!r||"error"===r.type)throw new s("no-response",{url:e.url})}catch(s){if(s instanceof Error)for(const i of t.iterateCallbacks("handlerDidError"))if(r=await i({error:s,event:n,request:e}),r)break;if(!r)throw s}for(const s of t.iterateCallbacks("handlerWillRespond"))r=await s({event:n,request:e,response:r});return r}async L(t,e,s,n){let r,i;try{r=await t}catch(i){}try{await e.runCallbacks("handlerDidRespond",{event:n,request:s,response:r}),await e.doneWaiting()}catch(t){t instanceof Error&&(i=t)}if(await e.runCallbacks("handlerDidComplete",{event:n,request:s,response:r,error:i}),e.destroy(),i)throw i}}function q(t,e){const s=e();return t.waitUntil(s),s}try{self["workbox:precaching:6.5.3"]&&_()}catch(t){}function U(t){if(!t)throw new s("add-to-cache-list-unexpected-type",{entry:t});if("string"==typeof t){const e=new URL(t,location.href);return{cacheKey:e.href,url:e.href}}const{revision:e,url:n}=t;if(!n)throw new s("add-to-cache-list-unexpected-type",{entry:t});if(!e){const t=new URL(n,location.href);return{cacheKey:t.href,url:t.href}}const r=new URL(n,location.href),i=new URL(n,location.href);return r.searchParams.set("__WB_REVISION__",e),{cacheKey:r.href,url:i.href}}class b{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:t,state:e})=>{e&&(e.originalRequest=t)},this.cachedResponseWillBeUsed=async({event:t,state:e,cachedResponse:s})=>{if("install"===t.type&&e&&e.originalRequest&&e.originalRequest instanceof Request){const t=e.originalRequest.url;s?this.notUpdatedURLs.push(t):this.updatedURLs.push(t)}return s}}}class L{constructor({precacheController:t}){this.cacheKeyWillBeUsed=async({request:t,params:e})=>{const s=(null==e?void 0:e.cacheKey)||this.C.getCacheKeyForURL(t.url);return s?new Request(s,{headers:t.headers}):t},this.C=t}}let C,E;async function O(t,e){let n=null;if(t.url){n=new URL(t.url).origin}if(n!==self.location.origin)throw new s("cross-origin-copy-response",{origin:n});const r=t.clone(),i={headers:new Headers(r.headers),status:r.status,statusText:r.statusText},o=e?e(i):i,c=function(){if(void 0===C){const t=new Response("");if("body"in t)try{new Response(t.body),C=!0}catch(t){C=!1}C=!1}return C}()?r.body:await r.blob();return new Response(c,o)}class x extends v{constructor(t={}){t.cacheName=w(t.cacheName),super(t),this.O=!1!==t.fallbackToNetwork,this.plugins.push(x.copyRedirectedCacheableResponsesPlugin)}async _(t,e){const s=await e.cacheMatch(t);return s||(e.event&&"install"===e.event.type?await this.N(t,e):await this.j(t,e))}async j(t,e){let n;const r=e.params||{};if(!this.O)throw new s("missing-precache-entry",{cacheName:this.cacheName,url:t.url});{const s=r.integrity,i=t.integrity,o=!i||i===s;n=await e.fetch(new Request(t,{integrity:"no-cors"!==t.mode?i||s:void 0})),s&&o&&"no-cors"!==t.mode&&(this.k(),await e.cachePut(t,n.clone()))}return n}async N(t,e){this.k();const n=await e.fetch(t);if(!await e.cachePut(t,n.clone()))throw new s("bad-precaching-response",{url:t.url,status:n.status});return n}k(){let t=null,e=0;for(const[s,n]of this.plugins.entries())n!==x.copyRedirectedCacheableResponsesPlugin&&(n===x.defaultPrecacheCacheabilityPlugin&&(t=s),n.cacheWillUpdate&&e++);0===e?this.plugins.push(x.defaultPrecacheCacheabilityPlugin):e>1&&null!==t&&this.plugins.splice(t,1)}}x.defaultPrecacheCacheabilityPlugin={cacheWillUpdate:async({response:t})=>!t||t.status>=400?null:t},x.copyRedirectedCacheableResponsesPlugin={cacheWillUpdate:async({response:t})=>t.redirected?await O(t):t};class N{constructor({cacheName:t,plugins:e=[],fallbackToNetwork:s=!0}={}){this.K=new Map,this.P=new Map,this.T=new Map,this.p=new x({cacheName:w(t),plugins:[...e,new L({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this.p}precache(t){this.addToCacheList(t),this.W||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this.W=!0)}addToCacheList(t){const e=[];for(const n of t){"string"==typeof n?e.push(n):n&&void 0===n.revision&&e.push(n.url);const{cacheKey:t,url:r}=U(n),i="string"!=typeof n&&n.revision?"reload":"default";if(this.K.has(r)&&this.K.get(r)!==t)throw new s("add-to-cache-list-conflicting-entries",{firstEntry:this.K.get(r),secondEntry:t});if("string"!=typeof n&&n.integrity){if(this.T.has(t)&&this.T.get(t)!==n.integrity)throw new s("add-to-cache-list-conflicting-integrities",{url:r});this.T.set(t,n.integrity)}if(this.K.set(r,t),this.P.set(r,i),e.length>0){const t=`Workbox is precaching URLs without revision info: ${e.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(t)}}}install(t){return q(t,(async()=>{const e=new b;this.strategy.plugins.push(e);for(const[e,s]of this.K){const n=this.T.get(s),r=this.P.get(e),i=new Request(e,{integrity:n,cache:r,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:s},request:i,event:t}))}const{updatedURLs:s,notUpdatedURLs:n}=e;return{updatedURLs:s,notUpdatedURLs:n}}))}activate(t){return q(t,(async()=>{const t=await self.caches.open(this.strategy.cacheName),e=await t.keys(),s=new Set(this.K.values()),n=[];for(const r of e)s.has(r.url)||(await t.delete(r),n.push(r.url));return{deletedURLs:n}}))}getURLsToCacheKeys(){return this.K}getCachedURLs(){return[...this.K.keys()]}getCacheKeyForURL(t){const e=new URL(t,location.href);return this.K.get(e.href)}getIntegrityForCacheKey(t){return this.T.get(t)}async matchPrecache(t){const e=t instanceof Request?t.url:t,s=this.getCacheKeyForURL(e);if(s){return(await self.caches.open(this.strategy.cacheName)).match(s)}}createHandlerBoundToURL(t){const e=this.getCacheKeyForURL(t);if(!e)throw new s("non-precached-url",{url:t});return s=>(s.request=new Request(t),s.params=Object.assign({cacheKey:e},s.params),this.strategy.handle(s))}}const j=()=>(E||(E=new N),E);class k extends r{constructor(t,e){super((({request:s})=>{const n=t.getURLsToCacheKeys();for(const r of function*(t,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:s="index.html",cleanURLs:n=!0,urlManipulation:r}={}){const i=new URL(t,location.href);i.hash="",yield i.href;const o=function(t,e=[]){for(const s of[...t.searchParams.keys()])e.some((t=>t.test(s)))&&t.searchParams.delete(s);return t}(i,e);if(yield o.href,s&&o.pathname.endsWith("/")){const t=new URL(o.href);t.pathname+=s,yield t.href}if(n){const t=new URL(o.href);t.pathname+=".html",yield t.href}if(r){const t=r({url:i});for(const e of t)yield e.href}}(s.url,e)){const e=n.get(r);if(e){return{cacheKey:e,integrity:t.getIntegrityForCacheKey(e)}}}}),t.strategy)}}t.CacheFirst=class extends v{async _(t,e){let n,r=await e.cacheMatch(t);if(!r)try{r=await e.fetchAndCachePut(t)}catch(t){t instanceof Error&&(n=t)}if(!r)throw new s("no-response",{url:t.url,error:n});return r}},t.CacheableResponsePlugin=class{constructor(t){this.cacheWillUpdate=async({response:t})=>this.M.isResponseCacheable(t)?t:null,this.M=new u(t)}},t.NavigationRoute=class extends r{constructor(t,{allowlist:e=[/./],denylist:s=[]}={}){super((t=>this.S(t)),t),this.D=e,this.F=s}S({url:t,request:e}){if(e&&"navigate"!==e.mode)return!1;const s=t.pathname+t.search;for(const t of this.F)if(t.test(s))return!1;return!!this.D.some((t=>t.test(s)))}},t.cleanupOutdatedCaches=function(){self.addEventListener("activate",(t=>{const e=w();t.waitUntil((async(t,e="-precache-")=>{const s=(await self.caches.keys()).filter((s=>s.includes(e)&&s.includes(self.registration.scope)&&s!==t));return await Promise.all(s.map((t=>self.caches.delete(t)))),s})(e).then((t=>{})))}))},t.clientsClaim=function(){self.addEventListener("activate",(()=>self.clients.claim()))},t.createHandlerBoundToURL=function(t){return j().createHandlerBoundToURL(t)},t.precacheAndRoute=function(t,e){!function(t){j().precache(t)}(t),function(t){const e=j();h(new k(e,t))}(e)},t.registerRoute=h}));
2 |
--------------------------------------------------------------------------------