Repository: ShariqAnsari88/shoe-store-frontend
Branch: main
Commit: c5405a37a753
Files: 33
Total size: 64.7 KB
Directory structure:
gitextract_oskfoxu2/
├── .eslintrc.json
├── .gitignore
├── README.md
├── components/
│ ├── CartItem.jsx
│ ├── Footer.jsx
│ ├── Header.jsx
│ ├── HeroBanner.jsx
│ ├── Menu.jsx
│ ├── MenuMobile.jsx
│ ├── ProductCard.jsx
│ ├── ProductDetailsCarousel.jsx
│ ├── RelatedProducts.jsx
│ └── Wrapper.jsx
├── jsconfig.json
├── next.config.js
├── package.json
├── pages/
│ ├── _app.js
│ ├── _document.js
│ ├── api/
│ │ └── hello.js
│ ├── cart.js
│ ├── category/
│ │ └── [slug].js
│ ├── failed.js
│ ├── index.js
│ ├── product/
│ │ └── [slug].js
│ └── success.js
├── postcss.config.js
├── store/
│ ├── cartSlice.js
│ └── store.js
├── styles/
│ └── globals.css
├── tailwind.config.js
└── utils/
├── api.js
├── helper.js
└── urls.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.json
================================================
{
"extends": "next/core-web-vitals",
"rules": {
"react/no-unescaped-entities": "off",
"@next/next/no-page-custom-font": "off"
}
}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
.env
# vercel
.vercel
================================================
FILE: README.md
================================================
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
================================================
FILE: components/CartItem.jsx
================================================
import Image from "next/image";
import React from "react";
import { RiDeleteBin6Line } from "react-icons/ri";
import { updateCart, removeFromCart } from "@/store/cartSlice";
import { useDispatch } from "react-redux";
const CartItem = ({ data }) => {
const p = data.attributes;
const dispatch = useDispatch();
const updateCartItem = (e, key) => {
let payload = {
key,
val: key === "quantity" ? parseInt(e.target.value) : e.target.value,
id: data.id,
};
dispatch(updateCart(payload));
};
return (
<div className="flex py-5 gap-3 md:gap-5 border-b">
{/* IMAGE START */}
<div className="shrink-0 aspect-square w-[50px] md:w-[120px]">
<Image
src={p.thumbnail.data.attributes.url}
alt={p.name}
width={120}
height={120}
/>
</div>
{/* IMAGE END */}
<div className="w-full flex flex-col">
<div className="flex flex-col md:flex-row justify-between">
{/* PRODUCT TITLE */}
<div className="text-lg md:text-2xl font-semibold text-black/[0.8]">
{p.name}
</div>
{/* PRODUCT SUBTITLE */}
<div className="text-sm md:text-md font-medium text-black/[0.5] block md:hidden">
{p.subtitle}
</div>
{/* PRODUCT PRICE */}
<div className="text-sm md:text-md font-bold text-black/[0.5] mt-2">
MRP : ₹{p.price}
</div>
</div>
{/* PRODUCT SUBTITLE */}
<div className="text-md font-medium text-black/[0.5] hidden md:block">
{p.subtitle}
</div>
<div className="flex items-center justify-between mt-4">
<div className="flex items-center gap-2 md:gap-10 text-black/[0.5] text-sm md:text-md">
<div className="flex items-center gap-1">
<div className="font-semibold">Size:</div>
<select
className="hover:text-black"
onChange={(e) =>
updateCartItem(e, "selectedSize")
}
>
{p.size.data.map((item, i) => {
return (
<option
key={i}
value={item.size}
disabled={
!item.enabled ? true : false
}
selected={
data.selectedSize === item.size
}
>
{item.size}
</option>
);
})}
</select>
</div>
<div className="flex items-center gap-1">
<div className="font-semibold">Quantity:</div>
<select
className="hover:text-black"
onChange={(e) => updateCartItem(e, "quantity")}
>
{Array.from(
{ length: 10 },
(_, i) => i + 1
).map((q, i) => {
return (
<option
key={i}
value={q}
selected={data.quantity === q}
>
{q}
</option>
);
})}
</select>
</div>
</div>
<RiDeleteBin6Line
onClick={() =>
dispatch(removeFromCart({ id: data.id }))
}
className="cursor-pointer text-black/[0.5] hover:text-black text-[16px] md:text-[20px]"
/>
</div>
</div>
</div>
);
};
export default CartItem;
================================================
FILE: components/Footer.jsx
================================================
import Link from "next/link";
import React from "react";
import { FaFacebookF, FaTwitter, FaYoutube, FaInstagram } from "react-icons/fa";
import Wrapper from "./Wrapper";
const Footer = () => {
return (
<footer className="bg-black text-white pt-14 pb-3">
<Wrapper className="flex justify-between flex-col md:flex-row gap-[50px] md:gap-0">
{/* LEFT START */}
<div className="flex gap-[50px] md:gap-[75px] lg:gap-[100px] flex-col md:flex-row">
{/* MENU START */}
<div className="flex flex-col gap-3 shrink-0">
<div className="font-oswald font-medium uppercase text-sm cursor-pointer">
Find a store
</div>
<div className="font-oswald font-medium uppercase text-sm cursor-pointer">
become a partner
</div>
<div className="font-oswald font-medium uppercase text-sm cursor-pointer">
sign up for email
</div>
<div className="font-oswald font-medium uppercase text-sm cursor-pointer">
send us feedback
</div>
<div className="font-oswald font-medium uppercase text-sm cursor-pointer">
student discount
</div>
</div>
{/* MENU END */}
{/* NORMAL MENU START */}
<div className="flex gap-[50px] md:gap-[75px] lg:gap-[100px] shrink-0">
{/* MENU START */}
<div className="flex flex-col gap-3">
<div className="font-oswald font-medium uppercase text-sm">
get help
</div>
<div className="text-sm text-white/[0.5] hover:text-white cursor-pointer">
Order Status
</div>
<div className="text-sm text-white/[0.5] hover:text-white cursor-pointer">
Delivery
</div>
<div className="text-sm text-white/[0.5] hover:text-white cursor-pointer">
Returns
</div>
<div className="text-sm text-white/[0.5] hover:text-white cursor-pointer">
Payment Options
</div>
<div className="text-sm text-white/[0.5] hover:text-white cursor-pointer">
Contact Us
</div>
</div>
{/* MENU END */}
{/* MENU START */}
<div className="flex flex-col gap-3">
<div className="font-oswald font-medium uppercase text-sm">
About nike
</div>
<div className="text-sm text-white/[0.5] hover:text-white cursor-pointer">
News
</div>
<div className="text-sm text-white/[0.5] hover:text-white cursor-pointer">
Careers
</div>
<div className="text-sm text-white/[0.5] hover:text-white cursor-pointer">
Investors
</div>
<div className="text-sm text-white/[0.5] hover:text-white cursor-pointer">
Sustainability
</div>
</div>
{/* MENU END */}
</div>
{/* NORMAL MENU END */}
</div>
{/* LEFT END */}
{/* RIGHT START */}
<div className="flex gap-4 justify-center md:justify-start">
<div
onClick={() =>
window.open("https://facebook.com", "_blank")
}
className="w-10 h-10 rounded-full bg-white/[0.25] flex items-center justify-center text-black hover:bg-white/[0.5] cursor-pointer"
>
<FaFacebookF size={20} />
</div>
<Link
href="https://twitter.com"
className="w-10 h-10 rounded-full bg-white/[0.25] flex items-center justify-center text-black hover:bg-white/[0.5] cursor-pointer"
>
<FaTwitter size={20} />
</Link>
<div className="w-10 h-10 rounded-full bg-white/[0.25] flex items-center justify-center text-black hover:bg-white/[0.5] cursor-pointer">
<FaYoutube size={20} />
</div>
<div className="w-10 h-10 rounded-full bg-white/[0.25] flex items-center justify-center text-black hover:bg-white/[0.5] cursor-pointer">
<FaInstagram size={20} />
</div>
</div>
{/* RIGHT END */}
</Wrapper>
<Wrapper className="flex justify-between mt-10 flex-col md:flex-row gap-[10px] md:gap-0">
{/* LEFT START */}
<div className="text-[12px] text-white/[0.5] hover:text-white cursor-pointer text-center md:text-left">
© 2023 Nike, Inc. All Rights Reserved
</div>
{/* LEFT END */}
{/* RIGHT START */}
<div className="flex gap-2 md:gap-5 text-center md:text-left flex-wrap justify-center">
<div className="text-[12px] text-white/[0.5] hover:text-white cursor-pointer">
Guides
</div>
<div className="text-[12px] text-white/[0.5] hover:text-white cursor-pointer">
Terms of Sale
</div>
<div className="text-[12px] text-white/[0.5] hover:text-white cursor-pointer">
Terms of Use
</div>
<div className="text-[12px] text-white/[0.5] hover:text-white cursor-pointer">
Privacy Policy
</div>
</div>
{/* RIGHT END */}
</Wrapper>
</footer>
);
};
export default Footer;
================================================
FILE: components/Header.jsx
================================================
import React, { useState, useEffect } from "react";
import Wrapper from "./Wrapper";
import Link from "next/link";
import Menu from "./Menu";
import MenuMobile from "./MenuMobile";
import { IoMdHeartEmpty } from "react-icons/io";
import { BsCart } from "react-icons/bs";
import { BiMenuAltRight } from "react-icons/bi";
import { VscChromeClose } from "react-icons/vsc";
import { fetchDataFromApi } from "@/utils/api";
import { useSelector } from "react-redux";
const Header = () => {
const [mobileMenu, setMobileMenu] = useState(false);
const [showCatMenu, setShowCatMenu] = useState(false);
const [show, setShow] = useState("translate-y-0");
const [lastScrollY, setLastScrollY] = useState(0);
const [categories, setCategories] = useState(null);
const { cartItems } = useSelector((state) => state.cart);
const controlNavbar = () => {
if (window.scrollY > 200) {
if (window.scrollY > lastScrollY && !mobileMenu) {
setShow("-translate-y-[80px]");
} else {
setShow("shadow-sm");
}
} else {
setShow("translate-y-0");
}
setLastScrollY(window.scrollY);
};
useEffect(() => {
window.addEventListener("scroll", controlNavbar);
return () => {
window.removeEventListener("scroll", controlNavbar);
};
}, [lastScrollY]);
useEffect(() => {
fetchCategories();
}, []);
const fetchCategories = async () => {
const { data } = await fetchDataFromApi("/api/categories?populate=*");
setCategories(data);
};
return (
<header
className={`w-full h-[50px] md:h-[80px] bg-white flex items-center justify-between z-20 sticky top-0 transition-transform duration-300 ${show}`}
>
<Wrapper className="h-[60px] flex justify-between items-center">
<Link href="/">
<img src="/logo.svg" className="w-[40px] md:w-[60px]" />
</Link>
<Menu
showCatMenu={showCatMenu}
setShowCatMenu={setShowCatMenu}
categories={categories}
/>
{mobileMenu && (
<MenuMobile
showCatMenu={showCatMenu}
setShowCatMenu={setShowCatMenu}
setMobileMenu={setMobileMenu}
categories={categories}
/>
)}
<div className="flex items-center gap-2 text-black">
{/* Icon start */}
<div className="w-8 md:w-12 h-8 md:h-12 rounded-full flex justify-center items-center hover:bg-black/[0.05] cursor-pointer relative">
<IoMdHeartEmpty className="text-[19px] md:text-[24px]" />
<div className="h-[14px] md:h-[18px] min-w-[14px] md:min-w-[18px] rounded-full bg-red-600 absolute top-1 left-5 md:left-7 text-white text-[10px] md:text-[12px] flex justify-center items-center px-[2px] md:px-[5px]">
51
</div>
</div>
{/* Icon end */}
{/* Icon start */}
<Link href="/cart">
<div className="w-8 md:w-12 h-8 md:h-12 rounded-full flex justify-center items-center hover:bg-black/[0.05] cursor-pointer relative">
<BsCart className="text-[15px] md:text-[20px]" />
{cartItems.length > 0 && (
<div className="h-[14px] md:h-[18px] min-w-[14px] md:min-w-[18px] rounded-full bg-red-600 absolute top-1 left-5 md:left-7 text-white text-[10px] md:text-[12px] flex justify-center items-center px-[2px] md:px-[5px]">
{cartItems.length}
</div>
)}
</div>
</Link>
{/* Icon end */}
{/* Mobile icon start */}
<div className="w-8 md:w-12 h-8 md:h-12 rounded-full flex md:hidden justify-center items-center hover:bg-black/[0.05] cursor-pointer relative -mr-2">
{mobileMenu ? (
<VscChromeClose
className="text-[16px]"
onClick={() => setMobileMenu(false)}
/>
) : (
<BiMenuAltRight
className="text-[20px]"
onClick={() => setMobileMenu(true)}
/>
)}
</div>
{/* Mobile icon end */}
</div>
</Wrapper>
</header>
);
};
export default Header;
================================================
FILE: components/HeroBanner.jsx
================================================
import React from "react";
import "react-responsive-carousel/lib/styles/carousel.min.css"; // requires a loader
import { Carousel } from "react-responsive-carousel";
import { BiArrowBack } from "react-icons/bi";
const HeroBanner = () => {
return (
<div className="relative text-white text-[20px] w-full max-w-[1360px] mx-auto">
<Carousel
autoPlay={true}
infiniteLoop={true}
showThumbs={false}
showIndicators={false}
showStatus={false}
renderArrowPrev={(clickHandler, hasPrev) => (
<div
onClick={clickHandler}
className="absolute right-[31px] md:right-[51px] bottom-0 w-[30px] md:w-[50px] h-[30px] md:h-[50px] bg-black z-10 flex items-center justify-center cursor-pointer hover:opacity-90"
>
<BiArrowBack className="text-sm md:text-lg" />
</div>
)}
renderArrowNext={(clickHandler, hasNext) => (
<div
onClick={clickHandler}
className="absolute right-0 bottom-0 w-[30px] md:w-[50px] h-[30px] md:h-[50px] bg-black z-10 flex items-center justify-center cursor-pointer hover:opacity-90"
>
<BiArrowBack className="rotate-180 text-sm md:text-lg" />
</div>
)}
>
<div>
<img
src="/slide-1.png"
className="aspect-[16/10] md:aspect-auto object-cover"
/>
<div className="px-[15px] md:px-[40px] py-[10px] md:py-[25px] font-oswald bg-white absolute bottom-[25px] md:bottom-[75px] left-0 text-black/[0.9] text-[15px] md:text-[30px] uppercase font-medium cursor-pointer hover:opacity-90">
Shop now
</div>
</div>
<div>
<img
src="/slide-2.png"
className="aspect-[16/10] md:aspect-auto object-cover"
/>
<div className="px-[15px] md:px-[40px] py-[10px] md:py-[25px] font-oswald bg-white absolute bottom-[25px] md:bottom-[75px] left-0 text-black/[0.9] text-[15px] md:text-[30px] uppercase font-medium cursor-pointer hover:opacity-90">
Shop now
</div>
</div>
<div>
<img
src="/slide-3.png"
className="aspect-[16/10] md:aspect-auto object-cover"
/>
<div className="px-[15px] md:px-[40px] py-[10px] md:py-[25px] font-oswald bg-white absolute bottom-[25px] md:bottom-[75px] left-0 text-black/[0.9] text-[15px] md:text-[30px] uppercase font-medium cursor-pointer hover:opacity-90">
Shop now
</div>
</div>
</Carousel>
</div>
);
};
export default HeroBanner;
================================================
FILE: components/Menu.jsx
================================================
import React from "react";
import Link from "next/link";
import { BsChevronDown } from "react-icons/bs";
const data = [
{ id: 1, name: "Home", url: "/" },
{ id: 2, name: "About", url: "/about" },
{ id: 3, name: "Categories", subMenu: true },
{ id: 4, name: "Contact", url: "/contact" },
];
const subMenuData = [
{ id: 1, name: "Jordan", doc_count: 11 },
{ id: 2, name: "Sneakers", doc_count: 8 },
{ id: 3, name: "Running shoes", doc_count: 64 },
{ id: 4, name: "Football shoes", doc_count: 107 },
];
const Menu = ({ showCatMenu, setShowCatMenu, categories }) => {
return (
<ul className="hidden md:flex items-center gap-8 font-medium text-black">
{data.map((item) => {
return (
<React.Fragment key={item.id}>
{!!item?.subMenu ? (
<li
className="cursor-pointer flex items-center gap-2 relative"
onMouseEnter={() => setShowCatMenu(true)}
onMouseLeave={() => setShowCatMenu(false)}
>
{item.name}
<BsChevronDown size={14} />
{showCatMenu && (
<ul className="bg-white absolute top-6 left-0 min-w-[250px] px-1 py-1 text-black shadow-lg">
{categories?.map(
({ attributes: c, id }) => {
return (
<Link
key={id}
href={`/category/${c.slug}`}
onClick={() =>
setShowCatMenu(
false
)
}
>
<li className="h-12 flex justify-between items-center px-3 hover:bg-black/[0.03] rounded-md">
{c.name}
<span className="opacity-50 text-sm">
{`(${c.products.data.length})`}
</span>
</li>
</Link>
);
}
)}
</ul>
)}
</li>
) : (
<li className="cursor-pointer">
<Link href={item?.url}>{item.name}</Link>
</li>
)}
</React.Fragment>
);
})}
</ul>
);
};
export default Menu;
================================================
FILE: components/MenuMobile.jsx
================================================
import React from "react";
import Link from "next/link";
import { BsChevronDown } from "react-icons/bs";
const data = [
{ id: 1, name: "Home", url: "/" },
{ id: 2, name: "About", url: "/about" },
{ id: 3, name: "Categories", subMenu: true },
{ id: 4, name: "Contact", url: "/contact" },
];
const subMenuData = [
{ id: 1, name: "Jordan", doc_count: 11 },
{ id: 2, name: "Sneakers", doc_count: 8 },
{ id: 3, name: "Running shoes", doc_count: 64 },
{ id: 4, name: "Football shoes", doc_count: 107 },
];
const MenuMobile = ({
showCatMenu,
setShowCatMenu,
setMobileMenu,
categories,
}) => {
return (
<ul className="flex flex-col md:hidden font-bold absolute top-[50px] left-0 w-full h-[calc(100vh-50px)] bg-white border-t text-black">
{data.map((item) => {
return (
<React.Fragment key={item.id}>
{!!item?.subMenu ? (
<li
className="cursor-pointer py-4 px-5 border-b flex flex-col relative"
onClick={() => setShowCatMenu(!showCatMenu)}
>
<div className="flex justify-between items-center">
{item.name}
<BsChevronDown size={14} />
</div>
{showCatMenu && (
<ul className="bg-black/[0.05] -mx-5 mt-4 -mb-4">
{categories?.map(
({ attributes: c, id }) => {
return (
<Link
key={id}
href={`/category/${c.slug}`}
onClick={() => {
setShowCatMenu(
false
);
setMobileMenu(
false
);
}}
>
<li className="py-4 px-8 border-t flex justify-between">
{c.name}
<span className="opacity-50 text-sm">
{`(${c.products.data.length})`}
</span>
</li>
</Link>
);
}
)}
</ul>
)}
</li>
) : (
<li className="py-4 px-5 border-b">
<Link
href={item?.url}
onClick={() => setMobileMenu(false)}
>
{item.name}
</Link>
</li>
)}
</React.Fragment>
);
})}
</ul>
);
};
export default MenuMobile;
================================================
FILE: components/ProductCard.jsx
================================================
import { getDiscountedPricePercentage } from "@/utils/helper";
import Image from "next/image";
import Link from "next/link";
import React from "react";
const ProductCard = ({ data: { attributes: p, id } }) => {
return (
<Link
href={`/product/${p.slug}`}
className="transform overflow-hidden bg-white duration-200 hover:scale-105 cursor-pointer"
>
<Image
width={500}
height={500}
src={p.thumbnail.data.attributes.url}
alt={p.name}
/>
<div className="p-4 text-black/[0.9]">
<h2 className="text-lg font-medium">{p.name}</h2>
<div className="flex items-center text-black/[0.5]">
<p className="mr-2 text-lg font-semibold">
₹{p.price}
</p>
{p.original_price && (
<>
<p className="text-base font-medium line-through">
₹{p.original_price}
</p>
<p className="ml-auto text-base font-medium text-green-500">
{getDiscountedPricePercentage(
p.original_price,
p.price
)}
% off
</p>
</>
)}
</div>
</div>
</Link>
);
};
export default ProductCard;
================================================
FILE: components/ProductDetailsCarousel.jsx
================================================
import React from "react";
import "react-responsive-carousel/lib/styles/carousel.min.css"; // requires a loader
import { Carousel } from "react-responsive-carousel";
const ProductDetailsCarousel = ({ images }) => {
return (
<div className="text-white text-[20px] w-full max-w-[1360px] mx-auto sticky top-[50px]">
<Carousel
infiniteLoop={true}
showIndicators={false}
showStatus={false}
thumbWidth={60}
className="productCarousel"
>
{images?.map((img) => (
<img
key={img.id}
src={img.attributes.url}
alt={img.attributes.name}
/>
))}
{/* <img src="/p2.png" />
<img src="/p3.png" />
<img src="/p4.png" />
<img src="/p5.png" />
<img src="/p6.png" />
<img src="/p7.png" /> */}
</Carousel>
</div>
);
};
export default ProductDetailsCarousel;
================================================
FILE: components/RelatedProducts.jsx
================================================
import React from "react";
import Carousel from "react-multi-carousel";
import "react-multi-carousel/lib/styles.css";
import ProductCard from "./ProductCard";
const RelatedProducts = ({ products }) => {
const responsive = {
desktop: {
breakpoint: { max: 3000, min: 1024 },
items: 3,
},
tablet: {
breakpoint: { max: 1023, min: 464 },
items: 2,
},
mobile: {
breakpoint: { max: 767, min: 0 },
items: 1,
},
};
return (
<div className="mt-[50px] md:mt-[100px] mb-[100px] md:mb-0">
<div className="text-2xl font-bold mb-5">You Might Also Like</div>
<Carousel
responsive={responsive}
containerClass="-mx-[10px]"
itemClass="px-[10px]"
>
{products?.data?.map((product) => (
<ProductCard key={product?.id} data={product} />
))}
</Carousel>
</div>
);
};
export default RelatedProducts;
================================================
FILE: components/Wrapper.jsx
================================================
import React from "react";
const Wrapper = ({ children, className }) => {
return (
<div
className={`w-full max-w-[1280px] px-5 md:px-10 mx-auto ${
className || ""
}`}
>
{children}
</div>
);
};
export default Wrapper;
================================================
FILE: jsconfig.json
================================================
{
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}
================================================
FILE: next.config.js
================================================
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
eslint: {
ignoreDuringBuilds: true,
},
images: {
domains: ["res.cloudinary.com"],
},
};
module.exports = nextConfig;
================================================
FILE: package.json
================================================
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@next/font": "13.1.6",
"@reduxjs/toolkit": "^1.9.3",
"@stripe/react-stripe-js": "^1.16.5",
"@stripe/stripe-js": "^1.48.0",
"eslint": "8.34.0",
"eslint-config-next": "13.1.6",
"next": "13.1.6",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.7.1",
"react-markdown": "^8.0.5",
"react-multi-carousel": "^2.8.2",
"react-redux": "^8.0.5",
"react-responsive-carousel": "^3.2.23",
"react-toastify": "^9.1.1",
"swr": "^2.1.0"
},
"devDependencies": {
"autoprefixer": "^10.4.14",
"postcss": "^8.4.21",
"tailwindcss": "^3.2.7"
}
}
================================================
FILE: pages/_app.js
================================================
import "@/styles/globals.css";
import Head from "next/head";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { Provider } from "react-redux";
import store from "@/store/store";
export default function App({ Component, pageProps }) {
return (
<>
<Head>
<title>Online Shoe Store | JS Dev Hindi</title>
<meta
name="description"
content="Generated by create next app"
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<link rel="icon" href="/favicon.ico" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin="true"
/>
<link
href="https://fonts.googleapis.com/css2?family=Oswald:wght@200;300;400;500;600;700&family=Urbanist:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"
rel="stylesheet"
/>
</Head>
<Provider store={store}>
<Header />
<Component {...pageProps} />
<Footer />
</Provider>
</>
);
}
================================================
FILE: pages/_document.js
================================================
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
================================================
FILE: pages/api/hello.js
================================================
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
================================================
FILE: pages/cart.js
================================================
import React, { useMemo, useState } from "react";
import Image from "next/image";
import Link from "next/link";
import Wrapper from "@/components/Wrapper";
import CartItem from "@/components/CartItem";
import { useSelector } from "react-redux";
import { makePaymentRequest } from "@/utils/api";
import { loadStripe } from "@stripe/stripe-js";
const stripePromise = loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
);
const Cart = () => {
const [loading, setLoading] = useState(false);
const { cartItems } = useSelector((state) => state.cart);
const subTotal = useMemo(() => {
return cartItems.reduce(
(total, val) => total + val.attributes.price,
0
);
}, [cartItems]);
const handlePayment = async () => {
try {
setLoading(true);
const stripe = await stripePromise;
const res = await makePaymentRequest("/api/orders", {
products: cartItems,
});
await stripe.redirectToCheckout({
sessionId: res.stripeSession.id,
});
} catch (error) {
setLoading(false);
console.log(error);
}
};
return (
<div className="w-full md:py-20">
<Wrapper>
{cartItems.length > 0 && (
<>
{/* HEADING AND PARAGRAPH START */}
<div className="text-center max-w-[800px] mx-auto mt-8 md:mt-0">
<div className="text-[28px] md:text-[34px] mb-5 font-semibold leading-tight">
Shopping Cart
</div>
</div>
{/* HEADING AND PARAGRAPH END */}
{/* CART CONTENT START */}
<div className="flex flex-col lg:flex-row gap-12 py-10">
{/* CART ITEMS START */}
<div className="flex-[2]">
<div className="text-lg font-bold">
Cart Items
</div>
{cartItems.map((item) => (
<CartItem key={item.id} data={item} />
))}
</div>
{/* CART ITEMS END */}
{/* SUMMARY START */}
<div className="flex-[1]">
<div className="text-lg font-bold">Summary</div>
<div className="p-5 my-5 bg-black/[0.05] rounded-xl">
<div className="flex justify-between">
<div className="uppercase text-md md:text-lg font-medium text-black">
Subtotal
</div>
<div className="text-md md:text-lg font-medium text-black">
₹{subTotal}
</div>
</div>
<div className="text-sm md:text-md py-5 border-t mt-5">
The subtotal reflects the total price of
your order, including duties and taxes,
before any applicable discounts. It does
not include delivery costs and
international transaction fees.
</div>
</div>
{/* BUTTON START */}
<button
className="w-full py-4 rounded-full bg-black text-white text-lg font-medium transition-transform active:scale-95 mb-3 hover:opacity-75 flex items-center gap-2 justify-center"
onClick={handlePayment}
>
Checkout
{loading && <img src="/spinner.svg" />}
</button>
{/* BUTTON END */}
</div>
{/* SUMMARY END */}
</div>
{/* CART CONTENT END */}
</>
)}
{/* This is empty screen */}
{cartItems.length < 1 && (
<div className="flex-[2] flex flex-col items-center pb-[50px] md:-mt-14">
<Image
src="/empty-cart.jpg"
width={300}
height={300}
className="w-[300px] md:w-[400px]"
/>
<span className="text-xl font-bold">
Your cart is empty
</span>
<span className="text-center mt-4">
Looks like you have not added anything in your cart.
<br />
Go ahead and explore top categories.
</span>
<Link
href="/"
className="py-4 px-8 rounded-full bg-black text-white text-lg font-medium transition-transform active:scale-95 mb-3 hover:opacity-75 mt-8"
>
Continue Shopping
</Link>
</div>
)}
</Wrapper>
</div>
);
};
export default Cart;
================================================
FILE: pages/category/[slug].js
================================================
import React, { useEffect, useState } from "react";
import Wrapper from "@/components/Wrapper";
import ProductCard from "@/components/ProductCard";
import { fetchDataFromApi } from "@/utils/api";
import useSWR from "swr";
import { useRouter } from "next/router";
const maxResult = 3;
const Category = ({ category, products, slug }) => {
const [pageIndex, setPageIndex] = useState(1);
const { query } = useRouter();
useEffect(() => {
setPageIndex(1);
}, [query]);
const { data, error, isLoading } = useSWR(
`/api/products?populate=*&[filters][categories][slug][$eq]=${slug}&pagination[page]=${pageIndex}&pagination[pageSize]=${maxResult}`,
fetchDataFromApi,
{
fallbackData: products,
}
);
return (
<div className="w-full md:py-20 relative">
<Wrapper>
<div className="text-center max-w-[800px] mx-auto mt-8 md:mt-0">
<div className="text-[28px] md:text-[34px] mb-5 font-semibold leading-tight">
{category?.data?.[0]?.attributes?.name}
</div>
</div>
{/* products grid start */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 my-14 px-5 md:px-0">
{data?.data?.map((product) => (
<ProductCard key={product?.id} data={product} />
))}
{/* <ProductCard />
<ProductCard />
<ProductCard />
<ProductCard />
<ProductCard />
<ProductCard />
<ProductCard />
<ProductCard />
<ProductCard /> */}
</div>
{/* products grid end */}
{/* PAGINATION BUTTONS START */}
{data?.meta?.pagination?.total > maxResult && (
<div className="flex gap-3 items-center justify-center my-16 md:my-0">
<button
className={`rounded py-2 px-4 bg-black text-white disabled:bg-gray-200 disabled:text-gray-500`}
disabled={pageIndex === 1}
onClick={() => setPageIndex(pageIndex - 1)}
>
Previous
</button>
<span className="font-bold">{`${pageIndex} of ${
data && data.meta.pagination.pageCount
}`}</span>
<button
className={`rounded py-2 px-4 bg-black text-white disabled:bg-gray-200 disabled:text-gray-500`}
disabled={
pageIndex ===
(data && data.meta.pagination.pageCount)
}
onClick={() => setPageIndex(pageIndex + 1)}
>
Next
</button>
</div>
)}
{/* PAGINATION BUTTONS END */}
{isLoading && (
<div className="absolute top-0 left-0 w-full h-full bg-white/[0.5] flex flex-col gap-5 justify-center items-center">
<img src="/logo.svg" width={150} />
<span className="text-2xl font-medium">Loading...</span>
</div>
)}
</Wrapper>
</div>
);
};
export default Category;
export async function getStaticPaths() {
const category = await fetchDataFromApi("/api/categories?populate=*");
const paths = category?.data?.map((c) => ({
params: {
slug: c.attributes.slug,
},
}));
return {
paths,
fallback: false,
};
}
// `getStaticPaths` requires using `getStaticProps`
export async function getStaticProps({ params: { slug } }) {
const category = await fetchDataFromApi(
`/api/categories?filters[slug][$eq]=${slug}`
);
const products = await fetchDataFromApi(
`/api/products?populate=*&[filters][categories][slug][$eq]=${slug}&pagination[page]=1&pagination[pageSize]=${maxResult}`
);
return {
props: {
category,
products,
slug,
},
};
}
================================================
FILE: pages/failed.js
================================================
import React from "react";
import Wrapper from "@/components/Wrapper";
import Link from "next/link";
const Failed = () => {
return (
<div className="min-h-[650px] flex items-center">
<Wrapper>
<div className="max-w-[600px] rounded-lg p-5 border border-black mx-auto flex flex-col">
<div className="text-2xl font-bold">Payment failed!</div>
<div className="text-base mt-5">
For any product related query, drop an email to
</div>
<div className="underline">shoeshopcontact@shop.com</div>
<Link href="/" className="font-bold mt-5">
Continue Shopping
</Link>
</div>
</Wrapper>
</div>
);
};
export default Failed;
================================================
FILE: pages/index.js
================================================
import HeroBanner from "@/components/HeroBanner";
import ProductCard from "@/components/ProductCard";
import Wrapper from "@/components/Wrapper";
import { fetchDataFromApi } from "@/utils/api";
export default function Home({ products }) {
return (
<main>
<HeroBanner />
<Wrapper>
{/* heading and paragaph start */}
<div className="text-center max-w-[800px] mx-auto my-[50px] md:my-[80px]">
<div className="text-[28px] md:text-[34px] mb-5 font-semibold leading-tight">
Cushioning for Your Miles
</div>
<div className="text-md md:text-xl">
A lightweight Nike ZoomX midsole is combined with
increased stack heights to help provide cushioning
during extended stretches of running.
</div>
</div>
{/* heading and paragaph end */}
{/* products grid start */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 my-14 px-5 md:px-0">
{products?.data?.map((product) => (
<ProductCard key={product?.id} data={product} />
))}
{/* <ProductCard />
<ProductCard />
<ProductCard />
<ProductCard />
<ProductCard />
<ProductCard />
<ProductCard />
<ProductCard />
<ProductCard /> */}
</div>
{/* products grid end */}
</Wrapper>
</main>
);
}
export async function getStaticProps() {
const products = await fetchDataFromApi("/api/products?populate=*");
return {
props: { products },
};
}
================================================
FILE: pages/product/[slug].js
================================================
import React, { useState } from "react";
import { IoMdHeartEmpty } from "react-icons/io";
import Wrapper from "@/components/Wrapper";
import ProductDetailsCarousel from "@/components/ProductDetailsCarousel";
import RelatedProducts from "@/components/RelatedProducts";
import { fetchDataFromApi } from "@/utils/api";
import { getDiscountedPricePercentage } from "@/utils/helper";
import ReactMarkdown from "react-markdown";
import { useSelector, useDispatch } from "react-redux";
import { addToCart } from "@/store/cartSlice";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
const ProductDetails = ({ product, products }) => {
const [selectedSize, setSelectedSize] = useState();
const [showError, setShowError] = useState(false);
const dispatch = useDispatch();
const p = product?.data?.[0]?.attributes;
const notify = () => {
toast.success("Success. Check your cart!", {
position: "bottom-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "dark",
});
};
return (
<div className="w-full md:py-20">
<ToastContainer />
<Wrapper>
<div className="flex flex-col lg:flex-row md:px-10 gap-[50px] lg:gap-[100px]">
{/* left column start */}
<div className="w-full md:w-auto flex-[1.5] max-w-[500px] lg:max-w-full mx-auto lg:mx-0">
<ProductDetailsCarousel images={p.image.data} />
</div>
{/* left column end */}
{/* right column start */}
<div className="flex-[1] py-3">
{/* PRODUCT TITLE */}
<div className="text-[34px] font-semibold mb-2 leading-tight">
{p.name}
</div>
{/* PRODUCT SUBTITLE */}
<div className="text-lg font-semibold mb-5">
{p.subtitle}
</div>
{/* PRODUCT PRICE */}
<div className="flex items-center">
<p className="mr-2 text-lg font-semibold">
MRP : ₹{p.price}
</p>
{p.original_price && (
<>
<p className="text-base font-medium line-through">
₹{p.original_price}
</p>
<p className="ml-auto text-base font-medium text-green-500">
{getDiscountedPricePercentage(
p.original_price,
p.price
)}
% off
</p>
</>
)}
</div>
<div className="text-md font-medium text-black/[0.5]">
incl. of taxes
</div>
<div className="text-md font-medium text-black/[0.5] mb-20">
{`(Also includes all applicable duties)`}
</div>
{/* PRODUCT SIZE RANGE START */}
<div className="mb-10">
{/* HEADING START */}
<div className="flex justify-between mb-2">
<div className="text-md font-semibold">
Select Size
</div>
<div className="text-md font-medium text-black/[0.5] cursor-pointer">
Select Guide
</div>
</div>
{/* HEADING END */}
{/* SIZE START */}
<div
id="sizesGrid"
className="grid grid-cols-3 gap-2"
>
{p.size.data.map((item, i) => (
<div
key={i}
className={`border rounded-md text-center py-3 font-medium ${
item.enabled
? "hover:border-black cursor-pointer"
: "cursor-not-allowed bg-black/[0.1] opacity-50"
} ${
selectedSize === item.size
? "border-black"
: ""
}`}
onClick={() => {
setSelectedSize(item.size);
setShowError(false);
}}
>
{item.size}
</div>
))}
</div>
{/* SIZE END */}
{/* SHOW ERROR START */}
{showError && (
<div className="text-red-600 mt-1">
Size selection is required
</div>
)}
{/* SHOW ERROR END */}
</div>
{/* PRODUCT SIZE RANGE END */}
{/* ADD TO CART BUTTON START */}
<button
className="w-full py-4 rounded-full bg-black text-white text-lg font-medium transition-transform active:scale-95 mb-3 hover:opacity-75"
onClick={() => {
if (!selectedSize) {
setShowError(true);
document
.getElementById("sizesGrid")
.scrollIntoView({
block: "center",
behavior: "smooth",
});
} else {
dispatch(
addToCart({
...product?.data?.[0],
selectedSize,
oneQuantityPrice: p.price,
})
);
notify();
}
}}
>
Add to Cart
</button>
{/* ADD TO CART BUTTON END */}
{/* WHISHLIST BUTTON START */}
<button className="w-full py-4 rounded-full border border-black text-lg font-medium transition-transform active:scale-95 flex items-center justify-center gap-2 hover:opacity-75 mb-10">
Whishlist
<IoMdHeartEmpty size={20} />
</button>
{/* WHISHLIST BUTTON END */}
<div>
<div className="text-lg font-bold mb-5">
Product Details
</div>
<div className="markdown text-md mb-5">
<ReactMarkdown>{p.description}</ReactMarkdown>
</div>
</div>
</div>
{/* right column end */}
</div>
<RelatedProducts products={products} />
</Wrapper>
</div>
);
};
export default ProductDetails;
export async function getStaticPaths() {
const products = await fetchDataFromApi("/api/products?populate=*");
const paths = products?.data?.map((p) => ({
params: {
slug: p.attributes.slug,
},
}));
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params: { slug } }) {
const product = await fetchDataFromApi(
`/api/products?populate=*&filters[slug][$eq]=${slug}`
);
const products = await fetchDataFromApi(
`/api/products?populate=*&[filters][slug][$ne]=${slug}`
);
return {
props: {
product,
products,
},
};
}
================================================
FILE: pages/success.js
================================================
import React from "react";
import Wrapper from "@/components/Wrapper";
import Link from "next/link";
const Success = () => {
return (
<div className="min-h-[650px] flex items-center">
<Wrapper>
<div className="max-w-[600px] rounded-lg p-5 border border-black mx-auto flex flex-col">
<div className="text-2xl font-bold">
Thanks for shopping with us!
</div>
<div className="text-lg font-bold mt-2">
Your order has been placed successfully.
</div>
<div className="text-base mt-5">
For any product related query, drop an email to
</div>
<div className="underline">shoeshopcontact@shop.com</div>
<Link href="/" className="font-bold mt-5">
Continue Shopping
</Link>
</div>
</Wrapper>
</div>
);
};
export default Success;
================================================
FILE: postcss.config.js
================================================
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
================================================
FILE: store/cartSlice.js
================================================
import { createSlice } from "@reduxjs/toolkit";
export const cartSlice = createSlice({
name: "cart",
initialState: {
cartItems: [],
},
reducers: {
addToCart: (state, action) => {
const item = state.cartItems.find(
(p) => p.id === action.payload.id
);
if (item) {
item.quantity++;
item.attributes.price = item.oneQuantityPrice * item.quantity;
} else {
state.cartItems.push({ ...action.payload, quantity: 1 });
}
},
updateCart: (state, action) => {
state.cartItems = state.cartItems.map((p) => {
if (p.id === action.payload.id) {
if (action.payload.key === "quantity") {
p.attributes.price =
p.oneQuantityPrice * action.payload.val;
}
return { ...p, [action.payload.key]: action.payload.val };
}
return p;
});
},
removeFromCart: (state, action) => {
state.cartItems = state.cartItems.filter(
(p) => p.id !== action.payload.id
);
},
},
});
// Action creators are generated for each case reducer function
export const { addToCart, updateCart, removeFromCart } = cartSlice.actions;
export default cartSlice.reducer;
================================================
FILE: store/store.js
================================================
import { configureStore } from "@reduxjs/toolkit";
import cartSlice from "./cartSlice";
export default configureStore({
reducer: {
cart: cartSlice,
},
});
================================================
FILE: styles/globals.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
font-family: "Urbanist", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 24px;
color: rgba(0, 0, 0, 1);
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
letter-spacing: 1px;
}
/* PRODUCT DETAILS CAROUSEL STYLING CUSTOMIZATION START */
.productCarousel.carousel-root {
display: flex;
flex-direction: row-reverse;
gap: 15px;
}
.productCarousel.carousel-root .carousel {
width: auto;
}
.productCarousel.carousel-root .carousel.carousel-slider {
width: 100%;
}
.productCarousel.carousel-root .carousel.carousel-slider .slider-wrapper {
border-radius: 10px;
}
.productCarousel.carousel-root .carousel .thumbs-wrapper {
margin: 0;
}
.productCarousel.carousel-root .carousel .thumb {
height: 60px;
border-radius: 6px;
overflow: hidden;
position: relative;
}
.productCarousel.carousel-root .carousel .thumb img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
.productCarousel.carousel-root .carousel .thumb:after {
content: "";
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background-color: black;
opacity: 0;
}
.productCarousel.carousel-root .carousel .thumb.selected,
.productCarousel.carousel-root .carousel .thumb:hover {
border: 0;
}
.productCarousel.carousel-root .carousel .thumb:hover:after,
.productCarousel.carousel-root .carousel .thumb.selected:after {
opacity: 0.2;
}
.productCarousel .control-arrow {
display: none;
}
@media screen and (max-width: 767px) {
.productCarousel.carousel-root {
flex-direction: column;
}
.productCarousel.carousel-root .carousel .thumb {
border: 0;
padding: 0;
}
}
@media screen and (min-width: 768px) {
.productCarousel.carousel-root .carousel .thumbs {
transform: none !important;
flex-direction: column;
display: flex;
gap: 10px;
}
.productCarousel.carousel-root .carousel .thumb {
border: 0;
padding: 0;
margin: 0;
}
}
/* PRODUCT DETAILS CAROUSEL STYLING CUSTOMIZATION END */
.markdown ul {
margin: 0;
padding: 0;
list-style-type: disc;
margin: 20px 0;
}
.markdown ul li {
margin: 10px 0;
}
================================================
FILE: tailwind.config.js
================================================
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx}",
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
// Or if using `src` directory:
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
fontFamily: {
oswald: "Oswald, sans-serif",
urbanist: "Urbanist, sans-serif",
},
extend: {},
},
plugins: [],
};
================================================
FILE: utils/api.js
================================================
import { API_URL, STRAPI_API_TOKEN } from "./urls";
export const fetchDataFromApi = async (endpoint) => {
const options = {
method: "GET",
headers: {
Authorization: "Bearer " + STRAPI_API_TOKEN,
},
};
const res = await fetch(`${API_URL}${endpoint}`, options);
const data = await res.json();
return data;
};
export const makePaymentRequest = async (endpoint, payload) => {
const res = await fetch(`${API_URL}${endpoint}`, {
method: "POST",
headers: {
Authorization: "Bearer " + STRAPI_API_TOKEN,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
const data = await res.json();
return data;
};
================================================
FILE: utils/helper.js
================================================
export const getDiscountedPricePercentage = (
originalPrice,
discountedPrice
) => {
const discount = originalPrice - discountedPrice;
const discountPercentage = (discount / originalPrice) * 100;
return discountPercentage.toFixed(2);
};
================================================
FILE: utils/urls.js
================================================
export const STRAPI_API_TOKEN = process.env.NEXT_PUBLIC_STRAPI_API_TOKEN;
export const API_URL =
process.env.NEXT_PUBLIC_API_URL || "http://127.0.0.1:1337";
gitextract_oskfoxu2/
├── .eslintrc.json
├── .gitignore
├── README.md
├── components/
│ ├── CartItem.jsx
│ ├── Footer.jsx
│ ├── Header.jsx
│ ├── HeroBanner.jsx
│ ├── Menu.jsx
│ ├── MenuMobile.jsx
│ ├── ProductCard.jsx
│ ├── ProductDetailsCarousel.jsx
│ ├── RelatedProducts.jsx
│ └── Wrapper.jsx
├── jsconfig.json
├── next.config.js
├── package.json
├── pages/
│ ├── _app.js
│ ├── _document.js
│ ├── api/
│ │ └── hello.js
│ ├── cart.js
│ ├── category/
│ │ └── [slug].js
│ ├── failed.js
│ ├── index.js
│ ├── product/
│ │ └── [slug].js
│ └── success.js
├── postcss.config.js
├── store/
│ ├── cartSlice.js
│ └── store.js
├── styles/
│ └── globals.css
├── tailwind.config.js
└── utils/
├── api.js
├── helper.js
└── urls.js
SYMBOL INDEX (11 symbols across 7 files)
FILE: pages/_app.js
function App (line 10) | function App({ Component, pageProps }) {
FILE: pages/_document.js
function Document (line 3) | function Document() {
FILE: pages/api/hello.js
function handler (line 3) | function handler(req, res) {
FILE: pages/category/[slug].js
function getStaticPaths (line 92) | async function getStaticPaths() {
function getStaticProps (line 107) | async function getStaticProps({ params: { slug } }) {
FILE: pages/index.js
function Home (line 5) | function Home({ products }) {
function getStaticProps (line 44) | async function getStaticProps() {
FILE: pages/product/[slug].js
function getStaticPaths (line 191) | async function getStaticPaths() {
function getStaticProps (line 205) | async function getStaticProps({ params: { slug } }) {
FILE: utils/urls.js
constant STRAPI_API_TOKEN (line 1) | const STRAPI_API_TOKEN = process.env.NEXT_PUBLIC_STRAPI_API_TOKEN;
constant API_URL (line 3) | const API_URL =
Condensed preview — 33 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (70K chars).
[
{
"path": ".eslintrc.json",
"chars": 158,
"preview": "{\n \"extends\": \"next/core-web-vitals\",\n \"rules\": {\n \"react/no-unescaped-entities\": \"off\",\n \"@next/nex"
},
{
"path": ".gitignore",
"chars": 348,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "README.md",
"chars": 1750,
"preview": "This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js"
},
{
"path": "components/CartItem.jsx",
"chars": 4992,
"preview": "import Image from \"next/image\";\nimport React from \"react\";\nimport { RiDeleteBin6Line } from \"react-icons/ri\";\nimport { u"
},
{
"path": "components/Footer.jsx",
"chars": 6818,
"preview": "import Link from \"next/link\";\nimport React from \"react\";\nimport { FaFacebookF, FaTwitter, FaYoutube, FaInstagram } from "
},
{
"path": "components/Header.jsx",
"chars": 4962,
"preview": "import React, { useState, useEffect } from \"react\";\nimport Wrapper from \"./Wrapper\";\n\nimport Link from \"next/link\";\nimpo"
},
{
"path": "components/HeroBanner.jsx",
"chars": 3154,
"preview": "import React from \"react\";\n\nimport \"react-responsive-carousel/lib/styles/carousel.min.css\"; // requires a loader\nimport "
},
{
"path": "components/Menu.jsx",
"chars": 3421,
"preview": "import React from \"react\";\nimport Link from \"next/link\";\nimport { BsChevronDown } from \"react-icons/bs\";\n\nconst data = ["
},
{
"path": "components/MenuMobile.jsx",
"chars": 3942,
"preview": "import React from \"react\";\nimport Link from \"next/link\";\nimport { BsChevronDown } from \"react-icons/bs\";\n\nconst data = ["
},
{
"path": "components/ProductCard.jsx",
"chars": 1626,
"preview": "import { getDiscountedPricePercentage } from \"@/utils/helper\";\nimport Image from \"next/image\";\nimport Link from \"next/li"
},
{
"path": "components/ProductDetailsCarousel.jsx",
"chars": 1117,
"preview": "import React from \"react\";\nimport \"react-responsive-carousel/lib/styles/carousel.min.css\"; // requires a loader\nimport {"
},
{
"path": "components/RelatedProducts.jsx",
"chars": 1079,
"preview": "import React from \"react\";\n\nimport Carousel from \"react-multi-carousel\";\nimport \"react-multi-carousel/lib/styles.css\";\ni"
},
{
"path": "components/Wrapper.jsx",
"chars": 303,
"preview": "import React from \"react\";\n\nconst Wrapper = ({ children, className }) => {\n return (\n <div\n classNa"
},
{
"path": "jsconfig.json",
"chars": 73,
"preview": "{\n \"compilerOptions\": {\n \"paths\": {\n \"@/*\": [\"./*\"]\n }\n }\n}\n"
},
{
"path": "next.config.js",
"chars": 239,
"preview": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n reactStrictMode: true,\n eslint: {\n ignoreDur"
},
{
"path": "package.json",
"chars": 932,
"preview": "{\n \"name\": \"frontend\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"next dev\",\n "
},
{
"path": "pages/_app.js",
"chars": 1502,
"preview": "import \"@/styles/globals.css\";\nimport Head from \"next/head\";\n\nimport Header from \"@/components/Header\";\nimport Footer fr"
},
{
"path": "pages/_document.js",
"chars": 231,
"preview": "import { Html, Head, Main, NextScript } from 'next/document'\n\nexport default function Document() {\n return (\n <Html "
},
{
"path": "pages/api/hello.js",
"chars": 170,
"preview": "// Next.js API route support: https://nextjs.org/docs/api-routes/introduction\n\nexport default function handler(req, res)"
},
{
"path": "pages/cart.js",
"chars": 5922,
"preview": "import React, { useMemo, useState } from \"react\";\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport W"
},
{
"path": "pages/category/[slug].js",
"chars": 4454,
"preview": "import React, { useEffect, useState } from \"react\";\nimport Wrapper from \"@/components/Wrapper\";\nimport ProductCard from "
},
{
"path": "pages/failed.js",
"chars": 860,
"preview": "import React from \"react\";\nimport Wrapper from \"@/components/Wrapper\";\nimport Link from \"next/link\";\n\nconst Failed = () "
},
{
"path": "pages/index.js",
"chars": 1916,
"preview": "import HeroBanner from \"@/components/HeroBanner\";\nimport ProductCard from \"@/components/ProductCard\";\nimport Wrapper fro"
},
{
"path": "pages/product/[slug].js",
"chars": 9399,
"preview": "import React, { useState } from \"react\";\nimport { IoMdHeartEmpty } from \"react-icons/io\";\nimport Wrapper from \"@/compone"
},
{
"path": "pages/success.js",
"chars": 1074,
"preview": "import React from \"react\";\nimport Wrapper from \"@/components/Wrapper\";\nimport Link from \"next/link\";\n\nconst Success = ()"
},
{
"path": "postcss.config.js",
"chars": 82,
"preview": "module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n}\n"
},
{
"path": "store/cartSlice.js",
"chars": 1439,
"preview": "import { createSlice } from \"@reduxjs/toolkit\";\n\nexport const cartSlice = createSlice({\n name: \"cart\",\n initialSta"
},
{
"path": "store/store.js",
"chars": 172,
"preview": "import { configureStore } from \"@reduxjs/toolkit\";\nimport cartSlice from \"./cartSlice\";\n\nexport default configureStore({"
},
{
"path": "styles/globals.css",
"chars": 2519,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n:root {\n font-family: \"Urbanist\", sans-serif;\n font-we"
},
{
"path": "tailwind.config.js",
"chars": 468,
"preview": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n content: [\n \"./app/**/*.{js,ts,jsx,tsx}\",\n "
},
{
"path": "utils/api.js",
"chars": 748,
"preview": "import { API_URL, STRAPI_API_TOKEN } from \"./urls\";\n\nexport const fetchDataFromApi = async (endpoint) => {\n const opt"
},
{
"path": "utils/helper.js",
"chars": 258,
"preview": "export const getDiscountedPricePercentage = (\n originalPrice,\n discountedPrice\n) => {\n const discount = origina"
},
{
"path": "utils/urls.js",
"chars": 162,
"preview": "export const STRAPI_API_TOKEN = process.env.NEXT_PUBLIC_STRAPI_API_TOKEN;\n\nexport const API_URL =\n process.env.NEXT_P"
}
]
About this extraction
This page contains the full source code of the ShariqAnsari88/shoe-store-frontend GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 33 files (64.7 KB), approximately 13.5k tokens, and a symbol index with 11 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.