├── .gitignore
├── server
├── constants
│ └── index.js
├── nodemon.json
├── public
│ └── images
│ │ ├── avatar.png
│ │ └── products
│ │ ├── cap.webp
│ │ ├── doll.webp
│ │ ├── table.webp
│ │ ├── watch.webp
│ │ ├── basket.webp
│ │ ├── clockBlack.webp
│ │ ├── eyeGlass.webp
│ │ ├── flowerBase.webp
│ │ ├── headPhone.webp
│ │ ├── roundBowl.webp
│ │ ├── backPackGray.webp
│ │ ├── backPackBlack.webp
│ │ └── productOfTheYear.webp
├── vercel.json
├── routes
│ ├── checkout.mjs
│ ├── dashboardRoute.mjs
│ ├── paymentRoute.js
│ ├── brandRoute.mjs
│ ├── categoryRoute.mjs
│ ├── contactRoute.js
│ ├── orderRoute.mjs
│ ├── productRoute.mjs
│ └── userRoute.mjs
├── .env
├── config
│ ├── mongodb.js
│ └── cloudinary.js
├── models
│ ├── categoryModel.js
│ ├── brandModel.js
│ ├── productModel.js
│ ├── contactModel.js
│ ├── userModel.js
│ └── orderModel.js
├── package.json
├── middleware
│ ├── avatarUpload.mjs
│ ├── userAuth.js
│ ├── adminAuth.js
│ └── multer.mjs
├── createAdmin.mjs
├── checkDB.mjs
├── index.mjs
└── controllers
│ └── checkoutController.mjs
├── client
├── src
│ ├── components
│ │ ├── Sale.jsx
│ │ ├── ui
│ │ │ ├── cn.js
│ │ │ ├── title.jsx
│ │ │ ├── text-design.jsx
│ │ │ └── button.jsx
│ │ ├── MobileNavigation.jsx
│ │ ├── Container.jsx
│ │ ├── Badge.jsx
│ │ ├── skeletons
│ │ │ ├── ProductSkeleton.jsx
│ │ │ └── ProductGridSkeleton.jsx
│ │ ├── Logout.jsx
│ │ ├── PreviousArrow.jsx
│ │ ├── NextArrow.jsx
│ │ ├── products
│ │ │ ├── NavTitle.jsx
│ │ │ ├── Price.jsx
│ │ │ ├── Brand.jsx
│ │ │ ├── Category.jsx
│ │ │ ├── Color.jsx
│ │ │ ├── ProductBanner.jsx
│ │ │ └── PaginationProductList.jsx
│ │ ├── PriceFormat.jsx
│ │ ├── MainLoader.jsx
│ │ ├── Breadcrumbs.jsx
│ │ ├── SocialLinks.jsx
│ │ ├── homeProducts
│ │ │ ├── ProductOfTheYear.jsx
│ │ │ ├── ProductInfo.jsx
│ │ │ ├── ProductsOnSale.jsx
│ │ │ ├── NewArrivals.jsx
│ │ │ ├── BestSellers.jsx
│ │ │ └── SpecialOffers.jsx
│ │ ├── CardProduct.jsx
│ │ ├── ScrollToTop.jsx
│ │ ├── RootLayout.jsx
│ │ ├── PriceContainer.jsx
│ │ ├── AddToCartButton.jsx
│ │ ├── PremiumModal.jsx
│ │ └── InteractiveMap.jsx
│ ├── constants
│ │ ├── index.js
│ │ └── navigation.js
│ ├── fonts
│ │ ├── suse.woff2
│ │ └── open_sans.woff2
│ ├── assets
│ │ ├── pdf
│ │ │ └── pdf1.pdf
│ │ ├── images
│ │ │ ├── payment.png
│ │ │ ├── contactUs.jpg
│ │ │ ├── emptyCart.png
│ │ │ ├── logoLight.png
│ │ │ ├── orebiLogo.png
│ │ │ ├── orebiLogoooo.png
│ │ │ ├── products
│ │ │ │ ├── cap.webp
│ │ │ │ ├── avatar.png
│ │ │ │ ├── basket.webp
│ │ │ │ ├── doll.webp
│ │ │ │ ├── table.webp
│ │ │ │ ├── watch.webp
│ │ │ │ ├── eyeGlass.webp
│ │ │ │ ├── clockBlack.webp
│ │ │ │ ├── flowerBase.webp
│ │ │ │ ├── headPhone.webp
│ │ │ │ ├── roundBowl.webp
│ │ │ │ ├── backPackBlack.webp
│ │ │ │ ├── backPackGray.webp
│ │ │ │ ├── productOfTheYear.webp
│ │ │ │ ├── newArrival
│ │ │ │ │ ├── newArrFour.webp
│ │ │ │ │ ├── newArrOne.webp
│ │ │ │ │ ├── newArrTwo.webp
│ │ │ │ │ └── newArrThree.webp
│ │ │ │ ├── specialOffer
│ │ │ │ │ ├── spfFour.webp
│ │ │ │ │ ├── spfOne.webp
│ │ │ │ │ ├── spfThree.webp
│ │ │ │ │ └── spfTwo.webp
│ │ │ │ └── bestSeller
│ │ │ │ │ ├── bestSellerFour.webp
│ │ │ │ │ ├── bestSellerOne.webp
│ │ │ │ │ ├── bestSellerThree.webp
│ │ │ │ │ └── bestSellerTwo.webp
│ │ │ ├── banner
│ │ │ │ ├── slideFour.png
│ │ │ │ ├── slideOne.png
│ │ │ │ ├── slideTwo.png
│ │ │ │ ├── slideThree.png
│ │ │ │ ├── bannerImgOne.jpg
│ │ │ │ ├── bannerImgThree.jpg
│ │ │ │ ├── bannerImgTwo.jpg
│ │ │ │ ├── bannerImgTwo.webp
│ │ │ │ └── bannerImgThree.webp
│ │ │ ├── slideOne-removebg-preview.png
│ │ │ └── index.js
│ │ └── products
│ │ │ ├── flower_base.jpg
│ │ │ ├── table_lamp.jpg
│ │ │ ├── wall_clock.jpg
│ │ │ ├── pottery_base.jpg
│ │ │ ├── deco_accessories.jpg
│ │ │ └── basket_with_handles.jpg
│ ├── pages
│ │ ├── Offers.jsx
│ │ ├── Product.jsx
│ │ ├── Contact.jsx
│ │ ├── Wishlist.jsx
│ │ └── NotFound.jsx
│ ├── styles
│ │ ├── index.css
│ │ └── base.css
│ ├── helpers
│ │ ├── index.js
│ │ ├── firebase.js
│ │ └── stockManager.js
│ ├── redux
│ │ ├── store.js
│ │ └── orebiSlice.js
│ ├── App.jsx
│ └── main.jsx
├── .env
├── vercel.json
├── public
│ ├── favicon.ico
│ ├── robots.txt
│ ├── manifest.json
│ ├── vite.svg
│ └── sitemap.xml
├── postcss.config.js
├── vite.config.js
├── .gitignore
├── README.md
├── config.js
├── tailwind.config.js
├── eslint.config.js
├── package.json
└── index.html
├── admin
├── .env
├── config.js
├── vercel.json
├── public
│ ├── favicon.ico
│ └── vite.svg
├── postcss.config.js
├── src
│ ├── assets
│ │ ├── images
│ │ │ ├── payment.png
│ │ │ ├── logoLight.png
│ │ │ ├── orebiLogo.png
│ │ │ ├── sale
│ │ │ │ ├── saleImgOne.webp
│ │ │ │ ├── saleImgTwo.webp
│ │ │ │ └── saleImgThree.webp
│ │ │ └── index.js
│ │ └── react.svg
│ ├── index.css
│ ├── components
│ │ ├── ui
│ │ │ ├── cn.js
│ │ │ ├── title.jsx
│ │ │ └── input.tsx
│ │ ├── Container.jsx
│ │ ├── ScrollToTop.jsx
│ │ ├── PriceFormat.jsx
│ │ ├── ProtectedRoute.jsx
│ │ ├── SmallLoader.jsx
│ │ ├── Loader.jsx
│ │ └── PremiumModal.jsx
│ ├── redux
│ │ ├── store.js
│ │ └── authSlice.js
│ ├── pages
│ │ ├── ApiDocumentation.jsx
│ │ ├── Contacts.jsx
│ │ ├── Analytics.jsx
│ │ ├── Inventory.jsx
│ │ └── Invoice.jsx
│ ├── main.jsx
│ ├── services
│ │ └── authService.js
│ ├── config
│ │ └── index.js
│ └── App.jsx
├── tailwind.config.js
├── vite.config.js
├── .gitignore
├── index.html
├── eslint.config.js
└── package.json
└── public
└── thumbnail.png
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/server/constants/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/components/Sale.jsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/constants/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/.env:
--------------------------------------------------------------------------------
1 | VITE_BACKEND_URL=http://localhost:8000
--------------------------------------------------------------------------------
/server/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "ignore": ["public/images/*"]
3 | }
4 |
--------------------------------------------------------------------------------
/admin/config.js:
--------------------------------------------------------------------------------
1 | export const serverUrl = import.meta.env.VITE_BACKEND_URL;
2 |
--------------------------------------------------------------------------------
/client/.env:
--------------------------------------------------------------------------------
1 | VITE_BACKEND_URL=http://localhost:8000
2 | VITE_STRIPE_PUBLISHABLE_KEY=
3 |
--------------------------------------------------------------------------------
/admin/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
3 | }
4 |
--------------------------------------------------------------------------------
/client/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
3 | }
4 |
--------------------------------------------------------------------------------
/public/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/public/thumbnail.png
--------------------------------------------------------------------------------
/admin/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/admin/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/src/fonts/suse.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/fonts/suse.woff2
--------------------------------------------------------------------------------
/client/src/assets/pdf/pdf1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/pdf/pdf1.pdf
--------------------------------------------------------------------------------
/admin/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/client/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/client/src/fonts/open_sans.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/fonts/open_sans.woff2
--------------------------------------------------------------------------------
/server/public/images/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/server/public/images/avatar.png
--------------------------------------------------------------------------------
/admin/src/assets/images/payment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/admin/src/assets/images/payment.png
--------------------------------------------------------------------------------
/admin/src/assets/images/logoLight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/admin/src/assets/images/logoLight.png
--------------------------------------------------------------------------------
/admin/src/assets/images/orebiLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/admin/src/assets/images/orebiLogo.png
--------------------------------------------------------------------------------
/client/src/assets/images/payment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/payment.png
--------------------------------------------------------------------------------
/client/src/assets/images/contactUs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/contactUs.jpg
--------------------------------------------------------------------------------
/client/src/assets/images/emptyCart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/emptyCart.png
--------------------------------------------------------------------------------
/client/src/assets/images/logoLight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/logoLight.png
--------------------------------------------------------------------------------
/client/src/assets/images/orebiLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/orebiLogo.png
--------------------------------------------------------------------------------
/server/public/images/products/cap.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/server/public/images/products/cap.webp
--------------------------------------------------------------------------------
/server/public/images/products/doll.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/server/public/images/products/doll.webp
--------------------------------------------------------------------------------
/server/public/images/products/table.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/server/public/images/products/table.webp
--------------------------------------------------------------------------------
/server/public/images/products/watch.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/server/public/images/products/watch.webp
--------------------------------------------------------------------------------
/client/src/assets/images/orebiLogoooo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/orebiLogoooo.png
--------------------------------------------------------------------------------
/client/src/assets/images/products/cap.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/cap.webp
--------------------------------------------------------------------------------
/client/src/assets/products/flower_base.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/products/flower_base.jpg
--------------------------------------------------------------------------------
/client/src/assets/products/table_lamp.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/products/table_lamp.jpg
--------------------------------------------------------------------------------
/client/src/assets/products/wall_clock.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/products/wall_clock.jpg
--------------------------------------------------------------------------------
/server/public/images/products/basket.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/server/public/images/products/basket.webp
--------------------------------------------------------------------------------
/admin/src/assets/images/sale/saleImgOne.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/admin/src/assets/images/sale/saleImgOne.webp
--------------------------------------------------------------------------------
/admin/src/assets/images/sale/saleImgTwo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/admin/src/assets/images/sale/saleImgTwo.webp
--------------------------------------------------------------------------------
/client/src/assets/images/banner/slideFour.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/banner/slideFour.png
--------------------------------------------------------------------------------
/client/src/assets/images/banner/slideOne.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/banner/slideOne.png
--------------------------------------------------------------------------------
/client/src/assets/images/banner/slideTwo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/banner/slideTwo.png
--------------------------------------------------------------------------------
/client/src/assets/images/products/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/avatar.png
--------------------------------------------------------------------------------
/client/src/assets/images/products/basket.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/basket.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/doll.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/doll.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/table.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/table.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/watch.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/watch.webp
--------------------------------------------------------------------------------
/client/src/assets/products/pottery_base.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/products/pottery_base.jpg
--------------------------------------------------------------------------------
/server/public/images/products/clockBlack.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/server/public/images/products/clockBlack.webp
--------------------------------------------------------------------------------
/server/public/images/products/eyeGlass.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/server/public/images/products/eyeGlass.webp
--------------------------------------------------------------------------------
/server/public/images/products/flowerBase.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/server/public/images/products/flowerBase.webp
--------------------------------------------------------------------------------
/server/public/images/products/headPhone.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/server/public/images/products/headPhone.webp
--------------------------------------------------------------------------------
/server/public/images/products/roundBowl.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/server/public/images/products/roundBowl.webp
--------------------------------------------------------------------------------
/admin/src/assets/images/index.js:
--------------------------------------------------------------------------------
1 | import logo from "./orebiLogo.png";
2 | import logoLight from "./logoLight.png";
3 |
4 | export { logo, logoLight };
5 |
--------------------------------------------------------------------------------
/admin/src/assets/images/sale/saleImgThree.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/admin/src/assets/images/sale/saleImgThree.webp
--------------------------------------------------------------------------------
/client/src/assets/images/banner/slideThree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/banner/slideThree.png
--------------------------------------------------------------------------------
/client/src/assets/images/products/eyeGlass.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/eyeGlass.webp
--------------------------------------------------------------------------------
/client/src/assets/products/deco_accessories.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/products/deco_accessories.jpg
--------------------------------------------------------------------------------
/server/public/images/products/backPackGray.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/server/public/images/products/backPackGray.webp
--------------------------------------------------------------------------------
/client/src/assets/images/banner/bannerImgOne.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/banner/bannerImgOne.jpg
--------------------------------------------------------------------------------
/client/src/assets/images/banner/bannerImgThree.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/banner/bannerImgThree.jpg
--------------------------------------------------------------------------------
/client/src/assets/images/banner/bannerImgTwo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/banner/bannerImgTwo.jpg
--------------------------------------------------------------------------------
/client/src/assets/images/banner/bannerImgTwo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/banner/bannerImgTwo.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/clockBlack.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/clockBlack.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/flowerBase.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/flowerBase.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/headPhone.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/headPhone.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/roundBowl.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/roundBowl.webp
--------------------------------------------------------------------------------
/client/src/assets/products/basket_with_handles.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/products/basket_with_handles.jpg
--------------------------------------------------------------------------------
/client/src/pages/Offers.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Offers = () => {
4 | return
Offers
;
5 | };
6 |
7 | export default Offers;
8 |
--------------------------------------------------------------------------------
/server/public/images/products/backPackBlack.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/server/public/images/products/backPackBlack.webp
--------------------------------------------------------------------------------
/admin/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | .active {
6 | background-color: black;
7 | color: white;
8 | }
9 |
--------------------------------------------------------------------------------
/client/src/assets/images/banner/bannerImgThree.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/banner/bannerImgThree.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/backPackBlack.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/backPackBlack.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/backPackGray.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/backPackGray.webp
--------------------------------------------------------------------------------
/client/src/pages/Product.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Product = () => {
4 | return Product
;
5 | };
6 |
7 | export default Product;
8 |
--------------------------------------------------------------------------------
/server/public/images/products/productOfTheYear.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/server/public/images/products/productOfTheYear.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/productOfTheYear.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/productOfTheYear.webp
--------------------------------------------------------------------------------
/client/src/assets/images/slideOne-removebg-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/slideOne-removebg-preview.png
--------------------------------------------------------------------------------
/client/src/styles/index.css:
--------------------------------------------------------------------------------
1 | @import "./base.css";
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | .scrollbar-hide::-webkit-scrollbar {
6 | display: none;
7 | }
8 |
--------------------------------------------------------------------------------
/client/src/assets/images/products/newArrival/newArrFour.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/newArrival/newArrFour.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/newArrival/newArrOne.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/newArrival/newArrOne.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/newArrival/newArrTwo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/newArrival/newArrTwo.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/specialOffer/spfFour.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/specialOffer/spfFour.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/specialOffer/spfOne.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/specialOffer/spfOne.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/specialOffer/spfThree.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/specialOffer/spfThree.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/specialOffer/spfTwo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/specialOffer/spfTwo.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/newArrival/newArrThree.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/newArrival/newArrThree.webp
--------------------------------------------------------------------------------
/admin/src/components/ui/cn.js:
--------------------------------------------------------------------------------
1 | import { clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 | export const cn = (...inputs) => {
4 | return twMerge(clsx(inputs));
5 | };
6 |
--------------------------------------------------------------------------------
/client/src/assets/images/products/bestSeller/bestSellerFour.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/bestSeller/bestSellerFour.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/bestSeller/bestSellerOne.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/bestSeller/bestSellerOne.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/bestSeller/bestSellerThree.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/bestSeller/bestSellerThree.webp
--------------------------------------------------------------------------------
/client/src/assets/images/products/bestSeller/bestSellerTwo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noorjsdivs/orebishopping-yt/HEAD/client/src/assets/images/products/bestSeller/bestSellerTwo.webp
--------------------------------------------------------------------------------
/client/src/components/ui/cn.js:
--------------------------------------------------------------------------------
1 | import { clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 | export const cn = (...inputs) => {
4 | return twMerge(clsx(inputs));
5 | };
6 |
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /cart
3 | Disallow: /orders
4 | Disallow: /signin
5 | Disallow: /signup
6 | Disallow: /profile
7 | Sitemap: https://orebiclient.reactbd.com/sitemap.xml
--------------------------------------------------------------------------------
/client/src/components/MobileNavigation.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const MobileNavigation = () => {
4 | return MobileNavigation
;
5 | };
6 |
7 | export default MobileNavigation;
8 |
--------------------------------------------------------------------------------
/admin/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | };
9 |
--------------------------------------------------------------------------------
/admin/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | server: { port: 5174 },
8 | });
9 |
--------------------------------------------------------------------------------
/client/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | server: { port: 5173 },
8 | });
9 |
--------------------------------------------------------------------------------
/admin/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import authReducer from "./authSlice";
3 |
4 | export const store = configureStore({
5 | reducer: {
6 | auth: authReducer,
7 | },
8 | });
9 |
10 | export default store;
11 |
--------------------------------------------------------------------------------
/client/src/components/ui/title.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { cn } from "./cn";
3 |
4 | const Title = ({ children, className }) => {
5 | return {children} ;
6 | };
7 |
8 | export default Title;
9 |
--------------------------------------------------------------------------------
/server/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "builds": [
4 | {
5 | "src": "index.mjs",
6 | "use": "@vercel/node"
7 | }
8 | ],
9 | "routes": [
10 | {
11 | "src": "/(.*)",
12 | "dest": "/index.mjs"
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/admin/src/components/Container.jsx:
--------------------------------------------------------------------------------
1 | import { cn } from "./ui/cn";
2 |
3 | const Container = ({ children, className }) => {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | };
10 |
11 | export default Container;
12 |
--------------------------------------------------------------------------------
/client/src/components/ui/text-design.jsx:
--------------------------------------------------------------------------------
1 | import { cn } from "./cn";
2 |
3 | const Title = ({ children, className }) => {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | };
10 |
11 | export default Title;
12 |
--------------------------------------------------------------------------------
/client/src/components/Container.jsx:
--------------------------------------------------------------------------------
1 | import { cn } from "./ui/cn";
2 |
3 | const Container = ({ children, className }) => {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | };
10 |
11 | export default Container;
12 |
--------------------------------------------------------------------------------
/server/routes/checkout.mjs:
--------------------------------------------------------------------------------
1 | import { Router } from "express";
2 | import { processCheckout } from "../controllers/checkoutController.mjs";
3 |
4 | const router = Router();
5 |
6 | // Process checkout and update product stock
7 | router.post("/api/checkout", processCheckout);
8 |
9 | export default router;
10 |
--------------------------------------------------------------------------------
/admin/src/components/ui/title.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { cn } from "./cn";
3 |
4 | const Title = ({ children, className }) => {
5 | return (
6 |
7 | {children}
8 |
9 | );
10 | };
11 |
12 | export default Title;
13 |
--------------------------------------------------------------------------------
/admin/src/components/ScrollToTop.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { useLocation } from "react-router-dom";
3 |
4 | function ScrollToTop() {
5 | const { pathname } = useLocation();
6 |
7 | useEffect(() => {
8 | window.scrollTo(0, 0);
9 | }, [pathname]);
10 |
11 | return null;
12 | }
13 |
14 | export default ScrollToTop;
15 |
--------------------------------------------------------------------------------
/client/src/components/Badge.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Badge = ({ text }) => {
4 | return (
5 |
6 | {text}
7 |
8 | );
9 | };
10 |
11 | export default Badge;
12 |
--------------------------------------------------------------------------------
/admin/.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 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/client/.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 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/server/.env:
--------------------------------------------------------------------------------
1 | PORT=8000
2 | NODE_ENV=development
3 | MONGO_URI=""
4 | # bpimern@gmail.com
5 | CLOUDINARY_NAME=
6 | CLOUDINARY_API_KEY=
7 | CLOUDINARY_SECRET_KEY=
8 | ADMIN_URL=http://localhost:5174
9 | CLIENT_URL=http://localhost:5173
10 | JWT_SECRET=your_jwt_secret
11 | ADMIN_EMAIL=
12 | ADMIN_PASSWORD=
13 | STRIPE_SECRET_KEY=
14 | STRIPE_PUBLISHABLE_KEY=
15 | STRIPE_WEBHOOK_SECRET=
--------------------------------------------------------------------------------
/server/config/mongodb.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const dbConnect = async () => {
4 | try {
5 | const conn = await mongoose.connect(process.env.MONGO_URI);
6 | console.log(`MongoDB Connected: ${conn.connection.host}`);
7 | } catch (error) {
8 | console.error(`Error: ${error.message}`);
9 | process.exit(1);
10 | }
11 | };
12 |
13 | export default dbConnect;
14 |
--------------------------------------------------------------------------------
/client/src/pages/Contact.jsx:
--------------------------------------------------------------------------------
1 | import PremiumMessage from "../components/PremiumMessage";
2 |
3 | const Contact = () => {
4 | return (
5 |
10 | );
11 | };
12 |
13 | export default Contact;
14 |
--------------------------------------------------------------------------------
/admin/src/components/PriceFormat.jsx:
--------------------------------------------------------------------------------
1 | import { cn } from "./ui/cn";
2 |
3 | const PriceFormat = ({ amount, className }) => {
4 | const formattedAmount = new Number(amount).toLocaleString("en-US", {
5 | style: "currency",
6 | currency: "USD",
7 | minimumFractionDigits: 2,
8 | });
9 | return {formattedAmount} ;
10 | };
11 |
12 | export default PriceFormat;
13 |
--------------------------------------------------------------------------------
/client/src/constants/navigation.js:
--------------------------------------------------------------------------------
1 | export const headerNavigation = [
2 | {
3 | title: "Home",
4 | link: "/",
5 | },
6 | {
7 | title: "Shop",
8 | link: "/shop",
9 | },
10 | {
11 | title: "About",
12 | link: "/about",
13 | },
14 | {
15 | title: "Contact",
16 | link: "/contact",
17 | },
18 | {
19 | title: "Orders",
20 | link: "/orders",
21 | },
22 | ];
23 |
--------------------------------------------------------------------------------
/client/src/helpers/index.js:
--------------------------------------------------------------------------------
1 | export const getData = async (endpoint) => {
2 | try {
3 | const response = await fetch(endpoint, {
4 | method: "GET",
5 | headers: {
6 | "Content-Type": "application/json",
7 | },
8 | });
9 | const data = await response.json();
10 | return data;
11 | } catch (error) {
12 | console.error("Error fetching products:", error);
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/admin/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Orebi Admin
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/client/src/styles/base.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | @font-face {
4 | font-family: "SUSE";
5 | font-style: normal;
6 | font-weight: 100 800;
7 | font-display: swap;
8 | src: url("../fonts/suse.woff2");
9 | }
10 |
11 | @font-face {
12 | font-family: "Open Sans";
13 | font-style: normal;
14 | font-weight: 300 800;
15 | font-stretch: 100%;
16 | font-display: swap;
17 | src: url("../fonts/open_sans.woff2");
18 | }
19 |
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Orebi Shopping",
3 | "short_name": "Orebi",
4 | "description": "Premium e-commerce shopping experience",
5 | "start_url": "/",
6 | "display": "standalone",
7 | "background_color": "#ffffff",
8 | "theme_color": "#000000",
9 | "icons": [
10 | {
11 | "src": "favicon.ico",
12 | "sizes": "64x64 32x32 24x24 16x16",
13 | "type": "image/x-icon"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/client/src/components/skeletons/ProductSkeleton.jsx:
--------------------------------------------------------------------------------
1 | const ProductSkeleton = () => (
2 |
10 | );
11 |
12 | export default ProductSkeleton;
13 |
--------------------------------------------------------------------------------
/client/config.js:
--------------------------------------------------------------------------------
1 | export const serverUrl = import.meta.env.VITE_BACKEND_URL;
2 |
3 | const checkConfig = (server) => {
4 | let config = {};
5 | switch (server) {
6 | case "production":
7 | config = {
8 | baseUrl: "http://localhost:8000",
9 | };
10 | break;
11 | case "local":
12 | config = {
13 | baseUrl: "http://localhost:8000",
14 | };
15 | break;
16 | default:
17 | break;
18 | }
19 | return config;
20 | };
21 |
22 | export const selectServer = "local";
23 | export const config = checkConfig(selectServer);
24 |
--------------------------------------------------------------------------------
/client/src/components/Logout.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button } from "./ui/button";
3 | import toast from "react-hot-toast";
4 | import { useNavigate } from "react-router-dom";
5 |
6 | const Logout = () => {
7 | const navigate = useNavigate();
8 |
9 | const handleLogout = async () => {
10 | localStorage.removeItem("token");
11 | toast.success("log out successfully");
12 | navigate("/");
13 | };
14 | return (
15 |
16 | Logout
17 |
18 | );
19 | };
20 |
21 | export default Logout;
22 |
--------------------------------------------------------------------------------
/client/src/components/PreviousArrow.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { FaLongArrowAltLeft } from "react-icons/fa";
3 |
4 | const PreviousArrow = (props) => {
5 | const { onClick } = props;
6 | return (
7 |
11 |
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | export default PreviousArrow;
19 |
--------------------------------------------------------------------------------
/server/routes/dashboardRoute.mjs:
--------------------------------------------------------------------------------
1 | import { Router } from "express";
2 | import {
3 | getDashboardStats,
4 | getAnalytics,
5 | getQuickStats,
6 | } from "../controllers/dashboardController.mjs";
7 | import adminAuth from "../middleware/adminAuth.js";
8 |
9 | const router = Router();
10 |
11 | const routeValue = "/api/dashboard/";
12 |
13 | // Admin dashboard routes
14 | router.get(`${routeValue}stats`, adminAuth, getDashboardStats);
15 | router.get(`${routeValue}analytics`, adminAuth, getAnalytics);
16 | router.get(`${routeValue}quick-stats`, adminAuth, getQuickStats);
17 |
18 | export default router;
19 |
--------------------------------------------------------------------------------
/client/src/components/NextArrow.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { FaLongArrowAltRight } from "react-icons/fa";
3 |
4 | const NextArrow = (props) => {
5 | const { onClick } = props;
6 | return (
7 |
11 |
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | export default NextArrow;
19 |
--------------------------------------------------------------------------------
/client/src/components/products/NavTitle.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { BiCaretDown } from "react-icons/bi";
3 |
4 | const NavTitle = ({ children, icons }) => {
5 | return (
6 |
7 | {icons ? (
8 | <>
9 |
{children}
10 | {icons && }
11 | >
12 | ) : (
13 | <>
14 | {children}
15 | >
16 | )}
17 |
18 | );
19 | };
20 |
21 | export default NavTitle;
22 |
--------------------------------------------------------------------------------
/client/src/helpers/firebase.js:
--------------------------------------------------------------------------------
1 | // Import the functions you need from the SDKs you need
2 | import { initializeApp } from "firebase/app";
3 | import { getAuth } from "firebase/auth";
4 | import { getFirestore } from "firebase/firestore";
5 | import { getStorage } from "firebase/storage";
6 |
7 | const firebaseConfig = {
8 | apiKey: "",
9 | authDomain: "",
10 | projectId: "",
11 | storageBucket: "",
12 | messagingSenderId: "",
13 | appId: "",
14 | measurementId: "",
15 | };
16 |
17 | const app = initializeApp(firebaseConfig);
18 | const auth = getAuth();
19 | const db = getFirestore();
20 | const storage = getStorage();
21 | export { app, auth, db, storage };
22 |
--------------------------------------------------------------------------------
/admin/src/components/ProtectedRoute.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import { Navigate, useLocation } from "react-router-dom";
3 | import PropTypes from "prop-types";
4 |
5 | const ProtectedRoute = ({ children }) => {
6 | const { isAuthenticated } = useSelector((state) => state.auth);
7 | const location = useLocation();
8 |
9 | if (!isAuthenticated) {
10 | // Redirect to login page with return url
11 | return ;
12 | }
13 |
14 | return children;
15 | };
16 |
17 | ProtectedRoute.propTypes = {
18 | children: PropTypes.node.isRequired,
19 | };
20 |
21 | export default ProtectedRoute;
22 |
--------------------------------------------------------------------------------
/client/tailwind.config.js:
--------------------------------------------------------------------------------
1 | import defaultTheme from "tailwindcss/defaultTheme";
2 | /** @type {import('tailwindcss').Config} */
3 | export default {
4 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
5 | theme: {
6 | extend: {
7 | colors: {
8 | primary: "#262626",
9 | lightText: "#6D6D6D",
10 | destructive: "#b91c1c",
11 | },
12 | boxShadow: {
13 | testShadow: "0px 0px 54px -13px rgba(0,0,0,0.7)",
14 | },
15 | fontFamily: {
16 | sans: ["Open Sans", ...defaultTheme.fontFamily.sans],
17 | titleFont: ["SUSE", "sans-serif"],
18 | },
19 | },
20 | },
21 | plugins: [],
22 | };
23 |
--------------------------------------------------------------------------------
/server/models/categoryModel.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const categorySchema = new mongoose.Schema({
4 | name: {
5 | type: String,
6 | required: true,
7 | unique: true,
8 | trim: true,
9 | },
10 | image: {
11 | type: String,
12 | required: true,
13 | },
14 | description: {
15 | type: String,
16 | trim: true,
17 | },
18 | isActive: {
19 | type: Boolean,
20 | default: true,
21 | },
22 | createdAt: {
23 | type: Date,
24 | default: Date.now,
25 | },
26 | });
27 |
28 | const categoryModel =
29 | mongoose.models.category || mongoose.model("category", categorySchema);
30 |
31 | export default categoryModel;
32 |
--------------------------------------------------------------------------------
/client/src/components/skeletons/ProductGridSkeleton.jsx:
--------------------------------------------------------------------------------
1 | import ProductSkeleton from "./ProductSkeleton";
2 |
3 | const ProductGridSkeleton = ({ title = "Loading...", count = 8 }) => {
4 | return (
5 |
6 |
9 |
10 | {Array.from({ length: count }).map((_, index) => (
11 |
12 | ))}
13 |
14 |
15 | );
16 | };
17 |
18 | export default ProductGridSkeleton;
19 |
--------------------------------------------------------------------------------
/client/src/components/PriceFormat.jsx:
--------------------------------------------------------------------------------
1 | import { cn } from "./ui/cn";
2 | import PropTypes from "prop-types";
3 |
4 | const PriceFormat = ({ amount, className }) => {
5 | // Handle undefined, null, or NaN values
6 | const numericAmount =
7 | typeof amount === "number" && !isNaN(amount) ? amount : 0;
8 |
9 | const formattedAmount = new Number(numericAmount).toLocaleString("en-US", {
10 | style: "currency",
11 | currency: "USD",
12 | minimumFractionDigits: 2,
13 | });
14 | return {formattedAmount} ;
15 | };
16 |
17 | PriceFormat.propTypes = {
18 | amount: PropTypes.number,
19 | className: PropTypes.string,
20 | };
21 |
22 | export default PriceFormat;
23 |
--------------------------------------------------------------------------------
/admin/src/pages/ApiDocumentation.jsx:
--------------------------------------------------------------------------------
1 | import PremiumMessage from "../components/PremiumMessage";
2 |
3 | const ApiDocumentation = () => {
4 | const apiFeatures = [
5 | "Complete REST API documentation",
6 | "Interactive API testing interface",
7 | "Real-time API monitoring",
8 | "Authentication endpoints",
9 | "Webhook integrations",
10 | "Developer support and resources",
11 | ];
12 |
13 | return (
14 |
19 | );
20 | };
21 |
22 | export default ApiDocumentation;
23 |
--------------------------------------------------------------------------------
/server/models/brandModel.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const brandSchema = new mongoose.Schema({
4 | name: {
5 | type: String,
6 | required: true,
7 | unique: true,
8 | trim: true,
9 | },
10 | image: {
11 | type: String,
12 | required: true,
13 | },
14 | description: {
15 | type: String,
16 | trim: true,
17 | },
18 | website: {
19 | type: String,
20 | trim: true,
21 | },
22 | isActive: {
23 | type: Boolean,
24 | default: true,
25 | },
26 | createdAt: {
27 | type: Date,
28 | default: Date.now,
29 | },
30 | });
31 |
32 | const brandModel =
33 | mongoose.models.brand || mongoose.model("brand", brandSchema);
34 |
35 | export default brandModel;
36 |
--------------------------------------------------------------------------------
/admin/src/pages/Contacts.jsx:
--------------------------------------------------------------------------------
1 | import PremiumMessage from "../components/PremiumMessage";
2 |
3 | const Contacts = () => {
4 | const contactFeatures = [
5 | "Advanced contact management system",
6 | "Automated email response templates",
7 | "Contact analytics and reporting",
8 | "Bulk contact operations",
9 | "Contact segmentation and filtering",
10 | "Priority support ticket system",
11 | ];
12 |
13 | return (
14 |
19 | );
20 | };
21 |
22 | export default Contacts;
23 |
--------------------------------------------------------------------------------
/admin/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { cn } from "./cn";
3 |
4 | export const Label = ({ htmlFor, children, className }) => {
5 | return (
6 |
10 | {children}
11 |
12 | );
13 | };
14 |
15 | const Input = ({ type, className, placeholder, id, name, onChange, value }) => {
16 | return (
17 |
29 | );
30 | };
31 |
32 | export default Input;
33 |
--------------------------------------------------------------------------------
/admin/src/main.jsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react";
2 | import { createRoot } from "react-dom/client";
3 | import { BrowserRouter } from "react-router-dom";
4 | import { Provider } from "react-redux";
5 | import { Toaster } from "react-hot-toast";
6 | import App from "./App.jsx";
7 | import { store } from "./redux/store.js";
8 | import "./index.css";
9 |
10 | createRoot(document.getElementById("root")).render(
11 |
12 |
13 |
14 |
15 |
24 |
25 |
26 |
27 | );
28 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.mjs",
6 | "type": "module",
7 | "scripts": {
8 | "start": "NODE_ENV=production node index.mjs",
9 | "dev": "NODE_ENV=development nodemon index.mjs",
10 | "server": "NODE_ENV=development nodemon index.mjs",
11 | "prod": "NODE_ENV=production node index.mjs"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "dependencies": {
17 | "bcrypt": "^5.1.1",
18 | "cloudinary": "^2.5.0",
19 | "cors": "^2.8.5",
20 | "dotenv": "^16.4.5",
21 | "express": "^4.21.0",
22 | "jsonwebtoken": "^9.0.2",
23 | "mongoose": "^8.9.5",
24 | "multer": "1.4.5-lts.1",
25 | "nodemon": "^3.1.4",
26 | "stripe": "^18.4.0",
27 | "validator": "^13.12.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/client/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import {
3 | persistStore,
4 | persistReducer,
5 | FLUSH,
6 | REHYDRATE,
7 | PAUSE,
8 | PERSIST,
9 | PURGE,
10 | REGISTER,
11 | } from "redux-persist";
12 | import storage from "redux-persist/lib/storage";
13 | import orebiReducer from "./orebiSlice";
14 |
15 | const persistConfig = {
16 | key: "root",
17 | version: 1,
18 | storage,
19 | };
20 |
21 | const persistedReducer = persistReducer(persistConfig, orebiReducer);
22 |
23 | export const store = configureStore({
24 | reducer: { orebiReducer: persistedReducer },
25 | middleware: (getDefaultMiddleware) =>
26 | getDefaultMiddleware({
27 | serializableCheck: {
28 | ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
29 | },
30 | }),
31 | });
32 |
33 | export let persistor = persistStore(store);
34 |
--------------------------------------------------------------------------------
/server/routes/paymentRoute.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | createPaymentIntent,
4 | confirmPayment,
5 | handleStripeWebhook,
6 | createOrder,
7 | } from "../controllers/paymentController.js";
8 | import userAuth from "../middleware/userAuth.js";
9 |
10 | const router = express.Router();
11 |
12 | const routeValue = "/api/payment/";
13 |
14 | // Create order
15 | router.post("/api/order/create", userAuth, createOrder);
16 |
17 | // Stripe payment routes
18 | router.post(
19 | `${routeValue}stripe/create-payment-intent`,
20 | userAuth,
21 | createPaymentIntent
22 | );
23 | router.post(`${routeValue}stripe/confirm-payment`, userAuth, confirmPayment);
24 |
25 | // Stripe webhook (no auth required)
26 | router.post(
27 | `${routeValue}stripe/webhook`,
28 | express.raw({ type: "application/json" }),
29 | handleStripeWebhook
30 | );
31 |
32 | export default router;
33 |
--------------------------------------------------------------------------------
/server/routes/brandRoute.mjs:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | createBrand,
4 | getBrands,
5 | getBrand,
6 | updateBrand,
7 | deleteBrand,
8 | } from "../controllers/brandController.mjs";
9 | import upload from "../middleware/multer.mjs";
10 | import adminAuth from "../middleware/adminAuth.js";
11 |
12 | const brandRouter = express.Router();
13 |
14 | const routeValue = "/api/brand";
15 |
16 | // Public routes
17 | brandRouter.get(`${routeValue}`, getBrands);
18 | brandRouter.get(`${routeValue}/:id`, getBrand);
19 |
20 | // Admin only routes
21 | brandRouter.post(
22 | `${routeValue}`,
23 | adminAuth,
24 | upload.single("image"),
25 | createBrand
26 | );
27 | brandRouter.put(
28 | `${routeValue}/:id`,
29 | adminAuth,
30 | upload.single("image"),
31 | updateBrand
32 | );
33 | brandRouter.delete(`${routeValue}/:id`, adminAuth, deleteBrand);
34 |
35 | export default brandRouter;
36 |
--------------------------------------------------------------------------------
/client/src/components/MainLoader.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { logo } from "../assets/images";
3 |
4 | const MainLoader = () => {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Orebi shopping is loading...
16 |
17 |
18 | );
19 | };
20 |
21 | export default MainLoader;
22 |
--------------------------------------------------------------------------------
/server/models/productModel.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const productSchema = new mongoose.Schema(
4 | {
5 | _type: { type: String },
6 | name: { type: String, required: true },
7 | images: { type: Array, required: true },
8 | price: { type: Number, required: true },
9 | discountedPercentage: { type: Number, required: true, default: 10 },
10 | stock: { type: Number, required: true, default: 0 },
11 | soldQuantity: { type: Number, default: 0 },
12 | category: { type: String, required: true },
13 | brand: { type: String },
14 | badge: { type: Boolean },
15 | isAvailable: { type: Boolean },
16 | offer: { type: Boolean },
17 | description: { type: String, required: true },
18 | tags: { type: Array },
19 | },
20 | {
21 | timestamps: true,
22 | }
23 | );
24 |
25 | const productModel =
26 | mongoose.models.product || mongoose.model("product", productSchema);
27 |
28 | export default productModel;
29 |
--------------------------------------------------------------------------------
/server/routes/categoryRoute.mjs:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | createCategory,
4 | getCategories,
5 | getCategory,
6 | updateCategory,
7 | deleteCategory,
8 | } from "../controllers/categoryController.mjs";
9 | import upload from "../middleware/multer.mjs";
10 | import adminAuth from "../middleware/adminAuth.js";
11 |
12 | const categoryRouter = express.Router();
13 |
14 | const routeValue = "/api/category";
15 |
16 | // Public routes
17 | categoryRouter.get(`${routeValue}`, getCategories);
18 | categoryRouter.get(`${routeValue}/:id`, getCategory);
19 |
20 | // Admin only routes
21 | categoryRouter.post(
22 | `${routeValue}`,
23 | adminAuth,
24 | upload.single("image"),
25 | createCategory
26 | );
27 | categoryRouter.put(
28 | `${routeValue}/:id`,
29 | adminAuth,
30 | upload.single("image"),
31 | updateCategory
32 | );
33 | categoryRouter.delete(`${routeValue}/:id`, adminAuth, deleteCategory);
34 |
35 | export default categoryRouter;
36 |
--------------------------------------------------------------------------------
/server/routes/contactRoute.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | createContact,
4 | getAllContacts,
5 | getContactById,
6 | updateContactStatus,
7 | deleteContact,
8 | getUserContacts,
9 | } from "../controllers/contactController.js";
10 | import userAuth from "../middleware/userAuth.js";
11 | import adminAuth from "../middleware/adminAuth.js";
12 |
13 | const router = express.Router();
14 |
15 | const routeValue = "/api/contact";
16 |
17 | // User routes (require authentication)
18 | router.post(`${routeValue}`, userAuth, createContact);
19 | router.get(`${routeValue}/my-contacts`, userAuth, getUserContacts);
20 |
21 | // Admin routes (require admin authentication)
22 | router.get(`${routeValue}/admin/all`, adminAuth, getAllContacts);
23 | router.get(`${routeValue}/admin/:id`, adminAuth, getContactById);
24 | router.put(`${routeValue}/admin/:id/status`, adminAuth, updateContactStatus);
25 | router.delete(`${routeValue}/admin/:id`, adminAuth, deleteContact);
26 |
27 | export default router;
28 |
--------------------------------------------------------------------------------
/server/routes/orderRoute.mjs:
--------------------------------------------------------------------------------
1 | import { Router } from "express";
2 | import {
3 | createOrder,
4 | getAllOrders,
5 | getUserOrders,
6 | getUserOrderById,
7 | updateOrderStatus,
8 | getOrderStats,
9 | deleteOrder,
10 | } from "../controllers/orderController.mjs";
11 | import adminAuth from "../middleware/adminAuth.js";
12 | import userAuth from "../middleware/userAuth.js";
13 |
14 | const router = Router();
15 |
16 | const routeValue = "/api/order/";
17 |
18 | // User routes (require authentication)
19 | router.post(`${routeValue}create`, userAuth, createOrder);
20 | router.get(`${routeValue}my-orders`, userAuth, getUserOrders);
21 | router.get(`${routeValue}user/:orderId`, userAuth, getUserOrderById);
22 |
23 | // Admin routes
24 | router.get(`${routeValue}admin/user/:userId`, adminAuth, getUserOrders);
25 | router.get(`${routeValue}list`, adminAuth, getAllOrders);
26 | router.get(`${routeValue}stats`, adminAuth, getOrderStats);
27 | router.post(`${routeValue}update-status`, adminAuth, updateOrderStatus);
28 | router.post(`${routeValue}delete`, adminAuth, deleteOrder);
29 |
30 | export default router;
31 |
--------------------------------------------------------------------------------
/server/middleware/avatarUpload.mjs:
--------------------------------------------------------------------------------
1 | import multer from "multer";
2 | import path from "path";
3 |
4 | // Configure multer for avatar uploads (storing temporarily for Cloudinary upload)
5 | const avatarStorage = multer.diskStorage({
6 | destination: function (req, file, cb) {
7 | cb(null, "./public/temp/"); // Temporary storage before Cloudinary upload
8 | },
9 | filename: function (req, file, cb) {
10 | // Generate unique filename with timestamp
11 | const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
12 | const ext = path.extname(file.originalname);
13 | cb(null, "temp-avatar-" + uniqueSuffix + ext);
14 | },
15 | });
16 |
17 | // File filter for images only
18 | const imageFilter = (req, file, cb) => {
19 | if (file.mimetype.startsWith("image/")) {
20 | cb(null, true);
21 | } else {
22 | cb(new Error("Only image files are allowed!"), false);
23 | }
24 | };
25 |
26 | const avatarUpload = multer({
27 | storage: avatarStorage,
28 | limits: {
29 | fileSize: 5 * 1024 * 1024, // 5MB limit
30 | },
31 | fileFilter: imageFilter,
32 | });
33 |
34 | export { avatarUpload };
35 |
--------------------------------------------------------------------------------
/admin/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import globals from 'globals'
3 | import react from 'eslint-plugin-react'
4 | import reactHooks from 'eslint-plugin-react-hooks'
5 | import reactRefresh from 'eslint-plugin-react-refresh'
6 |
7 | export default [
8 | { ignores: ['dist'] },
9 | {
10 | files: ['**/*.{js,jsx}'],
11 | languageOptions: {
12 | ecmaVersion: 2020,
13 | globals: globals.browser,
14 | parserOptions: {
15 | ecmaVersion: 'latest',
16 | ecmaFeatures: { jsx: true },
17 | sourceType: 'module',
18 | },
19 | },
20 | settings: { react: { version: '18.3' } },
21 | plugins: {
22 | react,
23 | 'react-hooks': reactHooks,
24 | 'react-refresh': reactRefresh,
25 | },
26 | rules: {
27 | ...js.configs.recommended.rules,
28 | ...react.configs.recommended.rules,
29 | ...react.configs['jsx-runtime'].rules,
30 | ...reactHooks.configs.recommended.rules,
31 | 'react/jsx-no-target-blank': 'off',
32 | 'react-refresh/only-export-components': [
33 | 'warn',
34 | { allowConstantExport: true },
35 | ],
36 | },
37 | },
38 | ]
39 |
--------------------------------------------------------------------------------
/client/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import globals from 'globals'
3 | import react from 'eslint-plugin-react'
4 | import reactHooks from 'eslint-plugin-react-hooks'
5 | import reactRefresh from 'eslint-plugin-react-refresh'
6 |
7 | export default [
8 | { ignores: ['dist'] },
9 | {
10 | files: ['**/*.{js,jsx}'],
11 | languageOptions: {
12 | ecmaVersion: 2020,
13 | globals: globals.browser,
14 | parserOptions: {
15 | ecmaVersion: 'latest',
16 | ecmaFeatures: { jsx: true },
17 | sourceType: 'module',
18 | },
19 | },
20 | settings: { react: { version: '18.3' } },
21 | plugins: {
22 | react,
23 | 'react-hooks': reactHooks,
24 | 'react-refresh': reactRefresh,
25 | },
26 | rules: {
27 | ...js.configs.recommended.rules,
28 | ...react.configs.recommended.rules,
29 | ...react.configs['jsx-runtime'].rules,
30 | ...reactHooks.configs.recommended.rules,
31 | 'react/jsx-no-target-blank': 'off',
32 | 'react-refresh/only-export-components': [
33 | 'warn',
34 | { allowConstantExport: true },
35 | ],
36 | },
37 | },
38 | ]
39 |
--------------------------------------------------------------------------------
/server/middleware/userAuth.js:
--------------------------------------------------------------------------------
1 | import jwt from "jsonwebtoken";
2 | import userModel from "../models/userModel.js";
3 |
4 | const userAuth = async (req, res, next) => {
5 | try {
6 | const authHeader = req.headers.authorization;
7 | const token =
8 | authHeader && authHeader.startsWith("Bearer ")
9 | ? authHeader.slice(7)
10 | : req.headers.token;
11 |
12 | if (!token) {
13 | return res.json({
14 | success: false,
15 | message: "Not Authorized, login required",
16 | });
17 | }
18 |
19 | const decoded = jwt.verify(token, process.env.JWT_SECRET);
20 |
21 | // Find user by ID from token
22 | const user = await userModel.findById(decoded.id);
23 |
24 | if (!user) {
25 | return res.json({ success: false, message: "User not found" });
26 | }
27 |
28 | if (!user.isActive) {
29 | return res.json({ success: false, message: "Account is deactivated" });
30 | }
31 |
32 | // Add user info to request object
33 | req.user = user;
34 | next();
35 | } catch (error) {
36 | console.log(error);
37 | res.json({ success: false, message: "Invalid token" });
38 | }
39 | };
40 |
41 | export default userAuth;
42 |
--------------------------------------------------------------------------------
/server/models/contactModel.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const contactSchema = new mongoose.Schema(
4 | {
5 | name: {
6 | type: String,
7 | required: true,
8 | trim: true,
9 | },
10 | email: {
11 | type: String,
12 | required: true,
13 | trim: true,
14 | lowercase: true,
15 | },
16 | subject: {
17 | type: String,
18 | required: true,
19 | trim: true,
20 | },
21 | message: {
22 | type: String,
23 | required: true,
24 | trim: true,
25 | },
26 | userId: {
27 | type: mongoose.Schema.Types.ObjectId,
28 | ref: "user",
29 | required: true,
30 | },
31 | status: {
32 | type: String,
33 | enum: ["unread", "read", "replied"],
34 | default: "unread",
35 | },
36 | adminNotes: {
37 | type: String,
38 | trim: true,
39 | default: "",
40 | },
41 | },
42 | {
43 | timestamps: true,
44 | }
45 | );
46 |
47 | // Index for better query performance
48 | contactSchema.index({ userId: 1 });
49 | contactSchema.index({ status: 1 });
50 | contactSchema.index({ createdAt: -1 });
51 |
52 | export default mongoose.model("Contact", contactSchema);
53 |
--------------------------------------------------------------------------------
/client/src/components/Breadcrumbs.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { HiOutlineChevronRight } from "react-icons/hi";
3 | import { useLocation } from "react-router-dom";
4 | import { cn } from "./ui/cn";
5 |
6 | const Breadcrumbs = ({ prevLocation, title, className }) => {
7 | const location = useLocation();
8 | const [locationPath, setLocationPath] = useState("");
9 | useEffect(() => {
10 | setLocationPath(location.pathname.split("/")[1]);
11 | }, [location]);
12 |
13 | return (
14 |
15 |
21 | {title}
22 |
23 |
24 | {prevLocation === "" ? "Home" : prevLocation}
25 |
26 |
27 |
28 |
29 |
30 | {locationPath}
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | export default Breadcrumbs;
38 |
--------------------------------------------------------------------------------
/server/middleware/adminAuth.js:
--------------------------------------------------------------------------------
1 | import jwt from "jsonwebtoken";
2 | import userModel from "../models/userModel.js";
3 |
4 | const adminAuth = async (req, res, next) => {
5 | try {
6 | const authHeader = req.headers.authorization;
7 | const token =
8 | authHeader && authHeader.startsWith("Bearer ")
9 | ? authHeader.slice(7)
10 | : req.headers.token;
11 |
12 | if (!token) {
13 | return res.json({ success: false, message: "Not Authorized, try again" });
14 | }
15 |
16 | const decoded = jwt.verify(token, process.env.JWT_SECRET);
17 |
18 | // Find user by ID from token
19 | const user = await userModel.findById(decoded.id);
20 |
21 | if (!user) {
22 | return res.json({ success: false, message: "User not found" });
23 | }
24 |
25 | if (user.role !== "admin") {
26 | return res.json({ success: false, message: "Admin access required" });
27 | }
28 |
29 | if (!user.isActive) {
30 | return res.json({ success: false, message: "Account is deactivated" });
31 | }
32 |
33 | // Add user info to request object
34 | req.user = user;
35 | next();
36 | } catch (error) {
37 | console.log(error);
38 | res.json({ success: false, message: "Invalid token" });
39 | }
40 | };
41 |
42 | export default adminAuth;
43 |
--------------------------------------------------------------------------------
/client/src/components/SocialLinks.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | FaEnvelope,
4 | FaFacebook,
5 | FaGithub,
6 | FaLinkedin,
7 | FaYoutube,
8 | } from "react-icons/fa";
9 | import { twMerge } from "tailwind-merge";
10 |
11 | const linkData = [
12 | { icon: , href: "https://github.com/" },
13 | { icon: , href: "https://www.youtube.com/@reactjsBD" },
14 | {
15 | icon: ,
16 | href: "https://www.linkedin.com/in/noor-mohammad-ab2245193/",
17 | },
18 | { icon: , href: "https://www.youtube.com/@reactjsBD" },
19 | { icon: , href: "https://www.youtube.com/@reactjsBD" },
20 | ];
21 |
22 | const SocialLinks = ({ className, iconStyle }) => {
23 | return (
24 |
43 | );
44 | };
45 |
46 | export default SocialLinks;
47 |
--------------------------------------------------------------------------------
/admin/src/services/authService.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { serverUrl } from "../../config";
3 |
4 | // Create axios instance with base configuration
5 | const api = axios.create({
6 | baseURL: serverUrl,
7 | headers: {
8 | "Content-Type": "application/json",
9 | },
10 | });
11 |
12 | // Add token to requests if available
13 | api.interceptors.request.use((config) => {
14 | const token = localStorage.getItem("token");
15 | if (token) {
16 | config.headers.Authorization = `Bearer ${token}`;
17 | }
18 | return config;
19 | });
20 |
21 | // Authentication service
22 | export const authService = {
23 | // Admin login
24 | adminLogin: async (credentials) => {
25 | const response = await api.post("/api/user/admin", credentials);
26 | return response.data;
27 | },
28 |
29 | // User login
30 | userLogin: async (credentials) => {
31 | const response = await api.post("/api/user/login", credentials);
32 | return response.data;
33 | },
34 |
35 | // User registration
36 | userRegister: async (userData) => {
37 | const response = await api.post("/api/user/register", userData);
38 | return response.data;
39 | },
40 |
41 | // Get user profile (if needed)
42 | getUserProfile: async () => {
43 | const response = await api.get("/api/user/profile");
44 | return response.data;
45 | },
46 | };
47 |
48 | export default authService;
49 |
--------------------------------------------------------------------------------
/admin/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/components/products/Price.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import NavTitle from "./NavTitle";
3 |
4 | const Price = () => {
5 | const priceList = [
6 | {
7 | _id: 950,
8 | priceOne: 0.0,
9 | priceTwo: 49.99,
10 | },
11 | {
12 | _id: 951,
13 | priceOne: 50.0,
14 | priceTwo: 99.99,
15 | },
16 | {
17 | _id: 952,
18 | priceOne: 100.0,
19 | priceTwo: 199.99,
20 | },
21 | {
22 | _id: 953,
23 | priceOne: 200.0,
24 | priceTwo: 399.99,
25 | },
26 | {
27 | _id: 954,
28 | priceOne: 400.0,
29 | priceTwo: 599.99,
30 | },
31 | {
32 | _id: 955,
33 | priceOne: 600.0,
34 | priceTwo: 1000.0,
35 | },
36 | ];
37 | return (
38 |
39 |
Shop by Price
40 |
41 |
42 | {priceList.map((item) => (
43 |
47 | ${item.priceOne.toFixed(2)} - ${item.priceTwo.toFixed(2)}
48 |
49 | ))}
50 |
51 |
52 |
53 | );
54 | };
55 |
56 | export default Price;
57 |
--------------------------------------------------------------------------------
/admin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "admin",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite --mode development",
8 | "dev:prod": "vite --mode production",
9 | "build": "vite build --mode production",
10 | "build:dev": "vite build --mode development",
11 | "lint": "eslint .",
12 | "preview": "vite preview --mode production",
13 | "preview:dev": "vite preview --mode development"
14 | },
15 | "dependencies": {
16 | "@headlessui/react": "^2.1.10",
17 | "@reduxjs/toolkit": "^2.8.2",
18 | "axios": "^1.7.7",
19 | "clsx": "^2.1.1",
20 | "framer-motion": "^12.23.12",
21 | "prop-types": "^15.8.1",
22 | "react": "^18.3.1",
23 | "react-dom": "^18.3.1",
24 | "react-hot-toast": "^2.4.1",
25 | "react-icons": "^5.3.0",
26 | "react-redux": "^9.2.0",
27 | "react-router-dom": "^6.26.2",
28 | "react-toastify": "^11.0.5",
29 | "tailwind-merge": "^2.5.3"
30 | },
31 | "devDependencies": {
32 | "@eslint/js": "^9.11.1",
33 | "@types/react": "^18.3.10",
34 | "@types/react-dom": "^18.3.0",
35 | "@vitejs/plugin-react": "^4.3.2",
36 | "autoprefixer": "^10.4.20",
37 | "eslint": "^9.11.1",
38 | "eslint-plugin-react": "^7.37.0",
39 | "eslint-plugin-react-hooks": "^5.1.0-rc.0",
40 | "eslint-plugin-react-refresh": "^0.4.12",
41 | "globals": "^15.9.0",
42 | "postcss": "^8.4.47",
43 | "tailwindcss": "^3.4.13",
44 | "vite": "^5.4.8"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/server/routes/productRoute.mjs:
--------------------------------------------------------------------------------
1 | import { Router } from "express";
2 | import {
3 | addProduct,
4 | listProducts,
5 | removeProduct,
6 | singleProducts,
7 | updateStock,
8 | updateProduct,
9 | } from "../controllers/productController.mjs";
10 | import upload from "../middleware/multer.mjs";
11 | import adminAuth from "../middleware/adminAuth.js";
12 |
13 | const router = Router();
14 |
15 | const routeValue = "/api/product/";
16 |
17 | // Admin routes for product management
18 | router.post(
19 | `${routeValue}add`,
20 | upload.fields([
21 | { name: "image1", maxCount: 1 },
22 | { name: "image2", maxCount: 1 },
23 | { name: "image3", maxCount: 1 },
24 | { name: "image4", maxCount: 1 },
25 | ]),
26 | adminAuth,
27 | addProduct
28 | );
29 | router.post(`${routeValue}remove`, adminAuth, removeProduct);
30 | router.put(
31 | `${routeValue}update/:id`,
32 | upload.fields([
33 | { name: "image1", maxCount: 1 },
34 | { name: "image2", maxCount: 1 },
35 | { name: "image3", maxCount: 1 },
36 | { name: "image4", maxCount: 1 },
37 | ]),
38 | adminAuth,
39 | updateProduct
40 | );
41 | router.post(`${routeValue}update-stock`, updateStock);
42 | router.get(`${routeValue}single`, singleProducts);
43 | router.get(`${routeValue}list`, listProducts);
44 |
45 | // Public routes for frontend
46 | router.get("/api/products", listProducts);
47 | router.get("/api/products/:type", (req, res, next) => {
48 | req.query._type = req.params.type;
49 | listProducts(req, res, next);
50 | });
51 |
52 | export default router;
53 |
--------------------------------------------------------------------------------
/server/models/userModel.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const userSchema = new mongoose.Schema(
4 | {
5 | name: { type: String, required: true },
6 | email: { type: String, required: true, unique: true },
7 | password: { type: String, required: true },
8 | role: {
9 | type: String,
10 | enum: ["admin", "user"],
11 | default: "user",
12 | },
13 | userCart: {
14 | type: Object,
15 | default: {},
16 | },
17 | orders: [
18 | {
19 | type: mongoose.Schema.Types.ObjectId,
20 | ref: "order",
21 | },
22 | ],
23 | addresses: [
24 | {
25 | label: { type: String, required: true }, // e.g., 'Home', 'Work', 'Billing'
26 | street: { type: String, required: true },
27 | city: { type: String, required: true },
28 | state: { type: String, required: true },
29 | zipCode: { type: String, required: true },
30 | country: { type: String, required: true },
31 | phone: { type: String, default: "" },
32 | isDefault: { type: Boolean, default: false },
33 | _id: { type: mongoose.Schema.Types.ObjectId, auto: true },
34 | },
35 | ],
36 | isActive: { type: Boolean, default: true },
37 | lastLogin: { type: Date },
38 | avatar: { type: String, default: "" },
39 | },
40 | {
41 | minimize: false,
42 | timestamps: true,
43 | }
44 | );
45 |
46 | // Index for better query performance
47 | userSchema.index({ role: 1 });
48 |
49 | const userModel = mongoose.models.user || mongoose.model("user", userSchema);
50 |
51 | export default userModel;
52 |
--------------------------------------------------------------------------------
/client/src/components/products/Brand.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { motion } from "framer-motion";
3 | import NavTitle from "./NavTitle";
4 |
5 | const Brand = () => {
6 | const [showBrands, setShowBrands] = useState(true);
7 | const brands = [
8 | {
9 | _id: 9006,
10 | title: "Apple",
11 | },
12 | {
13 | _id: 9007,
14 | title: "Ultron",
15 | },
16 | {
17 | _id: 9008,
18 | title: "Unknown",
19 | },
20 | {
21 | _id: 9009,
22 | title: "Shoppers Home",
23 | },
24 | {
25 | _id: 9010,
26 | title: "Hoichoi",
27 | },
28 | ];
29 |
30 | return (
31 |
32 |
setShowBrands(!showBrands)}
34 | className="cursor-pointer"
35 | >
36 | Shop by Brand
37 |
38 | {showBrands && (
39 |
44 |
45 | {brands.map((item) => (
46 |
50 | {item.title}
51 |
52 | ))}
53 |
54 |
55 | )}
56 |
57 | );
58 | };
59 |
60 | export default Brand;
61 |
--------------------------------------------------------------------------------
/client/src/components/products/Category.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 |
3 | import { ImPlus } from "react-icons/im";
4 | import NavTitle from "./NavTitle";
5 |
6 | const Category = () => {
7 | const [showSubCatOne, setShowSubCatOne] = useState(false);
8 | const items = [
9 | {
10 | _id: 990,
11 | title: "New Arrivals",
12 | icons: true,
13 | },
14 | {
15 | _id: 991,
16 | title: "Gudgets",
17 | },
18 | {
19 | _id: 992,
20 | title: "Accessories",
21 | icons: true,
22 | },
23 | {
24 | _id: 993,
25 | title: "Electronics",
26 | },
27 | {
28 | _id: 994,
29 | title: "Others",
30 | },
31 | ];
32 | return (
33 |
34 |
Shop by Category
35 |
36 |
37 | {items.map(({ _id, title, icons }) => (
38 |
42 | {title}
43 | {icons && (
44 | setShowSubCatOne(!showSubCatOne)}
46 | className="text-[10px] lg:text-xs cursor-pointer text-gray-400 hover:text-primeColor duration-300"
47 | >
48 |
49 |
50 | )}
51 |
52 | ))}
53 |
54 |
55 |
56 | );
57 | };
58 |
59 | export default Category;
60 |
--------------------------------------------------------------------------------
/server/createAdmin.mjs:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 | import bcrypt from "bcrypt";
3 | import userModel from "./models/userModel.js";
4 | import "dotenv/config";
5 |
6 | const createInitialAdmin = async () => {
7 | try {
8 | // Connect to MongoDB
9 | await mongoose.connect(process.env.MONGO_URI);
10 | console.log("Connected to MongoDB");
11 |
12 | // Check if admin already exists
13 | const existingAdmin = await userModel.findOne({ role: "admin" });
14 |
15 | if (existingAdmin) {
16 | console.log("Admin user already exists:", existingAdmin.email);
17 | process.exit(0);
18 | }
19 |
20 | // Create admin user
21 | const adminEmail = process.env.ADMIN_EMAIL || "admin@orebi.com";
22 | const adminPassword = process.env.ADMIN_PASSWORD || "admin123456";
23 | const adminName = process.env.ADMIN_NAME || "Admin User";
24 |
25 | // Hash password
26 | const salt = await bcrypt.genSalt(10);
27 | const hashedPassword = await bcrypt.hash(adminPassword, salt);
28 |
29 | const adminUser = new userModel({
30 | name: adminName,
31 | email: adminEmail,
32 | password: hashedPassword,
33 | role: "admin",
34 | isActive: true,
35 | });
36 |
37 | await adminUser.save();
38 |
39 | console.log("✅ Initial admin user created successfully!");
40 | console.log("Email:", adminEmail);
41 | console.log("Password:", adminPassword);
42 | console.log("⚠️ Please change the default password after first login");
43 | } catch (error) {
44 | console.error("Error creating admin user:", error);
45 | } finally {
46 | await mongoose.disconnect();
47 | console.log("Disconnected from MongoDB");
48 | process.exit(0);
49 | }
50 | };
51 |
52 | createInitialAdmin();
53 |
--------------------------------------------------------------------------------
/client/src/components/ui/button.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { cva } from "class-variance-authority";
3 | import { cn } from "./cn";
4 |
5 | const buttonVariants = cva(
6 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
7 | {
8 | variants: {
9 | variant: {
10 | default:
11 | "bg-primary/90 hover:bg-primary text-white/80 hover:text-white",
12 | destructive:
13 | "bg-destructive/90 text-white/90 hover:bg-destructive hover:text-white",
14 | outline:
15 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
16 | secondary:
17 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
18 | ghost: "hover:bg-accent hover:text-accent-foreground",
19 | link: "text-primary underline-offset-4 hover:underline",
20 | },
21 | size: {
22 | default: "h-10 px-4 py-2",
23 | sm: "h-9 rounded-md px-3",
24 | lg: "h-11 rounded-md px-8",
25 | icon: "h-10 w-10",
26 | },
27 | },
28 | defaultVariants: {
29 | variant: "default",
30 | size: "default",
31 | },
32 | }
33 | );
34 | const Button = React.forwardRef(
35 | ({ className, variant, size, asChild = false, ...props }, ref) => {
36 | const Comp = asChild ? Slot : "button";
37 | return (
38 |
43 | );
44 | }
45 | );
46 | Button.displayName = "Button";
47 |
48 | export { Button, buttonVariants };
49 |
--------------------------------------------------------------------------------
/client/src/components/homeProducts/ProductOfTheYear.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 | import { productOfTheYear } from "../../assets/images";
3 | import { Button } from "../ui/button";
4 |
5 | const ProductOfTheYear = () => {
6 | return (
7 |
8 |
9 |
14 |
15 |
16 |
17 | Product of The Year
18 |
19 |
20 | Discover our most innovative and popular product that has captured
21 | hearts worldwide. Experience excellence in every detail.
22 |
23 |
24 |
25 | Shop Now
26 |
27 |
28 |
29 |
30 | );
31 | };
32 |
33 | export default ProductOfTheYear;
34 |
--------------------------------------------------------------------------------
/client/src/assets/images/index.js:
--------------------------------------------------------------------------------
1 | import logo from "./orebiLogo.png";
2 | import logoLight from "./logoLight.png";
3 | import bannerImgOne from "./banner/slideOne.png";
4 | import bannerImgTwo from "./banner/slideTwo.png";
5 | import bannerImgThree from "./banner/slideThree.png";
6 | // ============== Products Start here ====================
7 |
8 | // Year Product
9 | import productOfTheYear from "./products/productOfTheYear.webp";
10 | // ============== Products End here ======================
11 | import paymentCard from "./payment.png";
12 | import emptyCart from "../images/emptyCart.png";
13 | // ProductList
14 | import backPackGray from "./products/backPackGray.webp";
15 | import backPackBlack from "./products/backPackBlack.webp";
16 | import basket from "./products/basket.webp";
17 | import cap from "./products/cap.webp";
18 | import clockBlack from "./products/clockBlack.webp";
19 | import doll from "./products/doll.webp";
20 | import eyeGlass from "./products/eyeGlass.webp";
21 | import flowerBase from "./products/flowerBase.webp";
22 | import headPhone from "./products/headPhone.webp";
23 | import roundBowl from "./products/roundBowl.webp";
24 | import table from "./products/table.webp";
25 | import watch from "./products/watch.webp";
26 | import avatar from "./products/avatar.png";
27 | import contactUs from "./contactUs.jpg";
28 |
29 | export {
30 | logo,
31 | logoLight,
32 | bannerImgOne,
33 | bannerImgTwo,
34 | bannerImgThree,
35 |
36 | // Year Product
37 | productOfTheYear,
38 | // ===================== Products End here ==============
39 | paymentCard,
40 | emptyCart,
41 | // productList
42 | backPackGray,
43 | backPackBlack,
44 | basket,
45 | cap,
46 | clockBlack,
47 | doll,
48 | eyeGlass,
49 | flowerBase,
50 | headPhone,
51 | roundBowl,
52 | table,
53 | watch,
54 | avatar,
55 | contactUs,
56 | };
57 |
--------------------------------------------------------------------------------
/server/middleware/multer.mjs:
--------------------------------------------------------------------------------
1 | import multer from "multer";
2 | import fs from "fs";
3 | import path from "path";
4 | import { fileURLToPath } from "url";
5 |
6 | // Get the directory name of the current module
7 | const __filename = fileURLToPath(import.meta.url);
8 | const __dirname = path.dirname(__filename);
9 |
10 | // Ensure temp directory exists
11 | const ensureTempDir = () => {
12 | const tempDir = path.join(__dirname, "../public/temp/");
13 | if (!fs.existsSync(tempDir)) {
14 | fs.mkdirSync(tempDir, { recursive: true });
15 | console.log("Created temp directory:", tempDir);
16 | }
17 | return tempDir;
18 | };
19 |
20 | const storage = multer.diskStorage({
21 | destination: function (req, file, callback) {
22 | // Ensure temp directory exists before using it
23 | try {
24 | const tempDir = ensureTempDir();
25 | callback(null, tempDir);
26 | } catch (error) {
27 | console.error("Error creating temp directory:", error);
28 | callback(error, null);
29 | }
30 | },
31 | filename: function (req, file, callback) {
32 | // Generate unique filename with timestamp
33 | const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
34 | const extension = path.extname(file.originalname);
35 | const nameWithoutExt = path.basename(file.originalname, extension);
36 | callback(null, uniqueSuffix + "-" + nameWithoutExt + extension);
37 | },
38 | });
39 |
40 | const upload = multer({
41 | storage,
42 | limits: {
43 | fileSize: 10 * 1024 * 1024, // 10MB limit
44 | },
45 | fileFilter: (req, file, callback) => {
46 | // Check if file is an image
47 | if (file.mimetype.startsWith("image/")) {
48 | callback(null, true);
49 | } else {
50 | callback(new Error("Only image files are allowed"), false);
51 | }
52 | },
53 | });
54 |
55 | export default upload;
56 |
--------------------------------------------------------------------------------
/admin/src/config/index.js:
--------------------------------------------------------------------------------
1 | // Admin Configuration
2 | const ENV = import.meta.env.MODE || "development";
3 |
4 | const config = {
5 | development: {
6 | API_BASE_URL: "http://localhost:3000",
7 | CLIENT_BASE_URL: "http://localhost:5173",
8 | ADMIN_BASE_URL: "http://localhost:5174",
9 | NODE_ENV: "development",
10 | DEBUG: true,
11 | LOG_LEVEL: "debug",
12 | },
13 | production: {
14 | API_BASE_URL:
15 | import.meta.env.VITE_API_BASE_URL || "https://your-api-domain.com",
16 | CLIENT_BASE_URL:
17 | import.meta.env.VITE_CLIENT_BASE_URL || "https://orebiclient.reactbd.com",
18 | ADMIN_BASE_URL:
19 | import.meta.env.VITE_ADMIN_BASE_URL || "https://orebiadmin.reactbd.com",
20 | NODE_ENV: "production",
21 | DEBUG: false,
22 | LOG_LEVEL: "error",
23 | },
24 | };
25 |
26 | // Export the configuration based on current environment
27 | const currentConfig = config[ENV] || config.development;
28 |
29 | export const {
30 | API_BASE_URL,
31 | CLIENT_BASE_URL,
32 | ADMIN_BASE_URL,
33 | NODE_ENV,
34 | DEBUG,
35 | LOG_LEVEL,
36 | } = currentConfig;
37 |
38 | // Legacy support for existing serverUrl import
39 | export const serverUrl = API_BASE_URL;
40 |
41 | // Environment check utilities
42 | export const isDevelopment = ENV === "development";
43 | export const isProduction = ENV === "production";
44 |
45 | // Logger utility
46 | export const logger = {
47 | debug: (...args) => {
48 | if (DEBUG) {
49 | console.log("[DEBUG]", ...args);
50 | }
51 | },
52 | info: (...args) => {
53 | if (DEBUG || LOG_LEVEL === "info") {
54 | console.info("[INFO]", ...args);
55 | }
56 | },
57 | warn: (...args) => {
58 | console.warn("[WARN]", ...args);
59 | },
60 | error: (...args) => {
61 | console.error("[ERROR]", ...args);
62 | },
63 | };
64 |
65 | export default currentConfig;
66 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite --mode development",
8 | "dev:prod": "vite --mode production",
9 | "build": "vite build --mode production",
10 | "build:dev": "vite build --mode development",
11 | "lint": "eslint .",
12 | "preview": "vite preview --mode production",
13 | "preview:dev": "vite preview --mode development"
14 | },
15 | "dependencies": {
16 | "@headlessui/react": "^2.1.8",
17 | "@reduxjs/toolkit": "^2.2.7",
18 | "@stripe/react-stripe-js": "^3.8.1",
19 | "@stripe/stripe-js": "^7.7.0",
20 | "@tailwindcss/line-clamp": "^0.4.4",
21 | "axios": "^1.7.7",
22 | "class-variance-authority": "^0.7.0",
23 | "clsx": "^2.1.1",
24 | "firebase": "^10.13.2",
25 | "framer-motion": "^11.5.4",
26 | "jwt-decode": "^4.0.0",
27 | "leaflet": "^1.9.4",
28 | "react": "^18.3.1",
29 | "react-dom": "^18.3.1",
30 | "react-hot-toast": "^2.4.1",
31 | "react-icons": "^5.3.0",
32 | "react-leaflet": "^5.0.0",
33 | "react-paginate": "^8.2.0",
34 | "react-redux": "^9.1.2",
35 | "react-router-dom": "^6.26.2",
36 | "react-slick": "^0.30.2",
37 | "redux-persist": "^6.0.0",
38 | "slick-carousel": "^1.8.1",
39 | "tailwind-merge": "^2.5.2"
40 | },
41 | "devDependencies": {
42 | "@eslint/js": "^9.9.0",
43 | "@types/react": "^18.3.3",
44 | "@types/react-dom": "^18.3.0",
45 | "@vitejs/plugin-react": "^4.3.1",
46 | "autoprefixer": "^10.4.20",
47 | "eslint": "^9.9.0",
48 | "eslint-plugin-react": "^7.35.0",
49 | "eslint-plugin-react-hooks": "^5.1.0-rc.0",
50 | "eslint-plugin-react-refresh": "^0.4.9",
51 | "globals": "^15.9.0",
52 | "postcss": "^8.4.45",
53 | "tailwindcss": "^3.4.11",
54 | "vite": "^5.4.1"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/client/src/components/CardProduct.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ImCross } from "react-icons/im";
3 | import { useDispatch } from "react-redux";
4 | import { deleteItem } from "../redux/orebiSlice";
5 | import AddToCartButton from "./AddToCartButton";
6 | import PriceFormat from "./PriceFormat";
7 | import PriceContainer from "./PriceContainer";
8 | import toast from "react-hot-toast";
9 |
10 | const CartProduct = ({ item }) => {
11 | const dispatch = useDispatch();
12 | return (
13 |
14 |
15 |
{
17 | dispatch(deleteItem(item._id));
18 | toast.success(
19 | `${item?.name.substring(0, 10)}... is deleted successfully!`
20 | );
21 | }}
22 | className="text-primeColor hover:text-red-500 duration-300 cursor-pointer"
23 | />
24 |
25 | {item.name}
26 |
27 |
39 |
40 | );
41 | };
42 |
43 | export default CartProduct;
44 |
--------------------------------------------------------------------------------
/client/src/components/products/Color.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { motion } from "framer-motion";
3 | import NavTitle from "./NavTitle";
4 |
5 | const Color = () => {
6 | const [showColors, setShowColors] = useState(true);
7 | const colors = [
8 | {
9 | _id: 9001,
10 | title: "Green",
11 | base: "#22c55e",
12 | },
13 | {
14 | _id: 9002,
15 | title: "Gray",
16 | base: "#a3a3a3",
17 | },
18 | {
19 | _id: 9003,
20 | title: "Red",
21 | base: "#dc2626",
22 | },
23 | {
24 | _id: 9004,
25 | title: "Yellow",
26 | base: "#f59e0b",
27 | },
28 | {
29 | _id: 9005,
30 | title: "Blue",
31 | base: "#3b82f6",
32 | },
33 | ];
34 |
35 | return (
36 |
37 |
setShowColors(!showColors)}
39 | className="cursor-pointer"
40 | >
41 | Shop by Color
42 |
43 | {showColors && (
44 |
49 |
50 | {colors.map((item) => (
51 |
55 |
59 | {item.title}
60 |
61 | ))}
62 |
63 |
64 | )}
65 |
66 | );
67 | };
68 |
69 | export default Color;
70 |
--------------------------------------------------------------------------------
/client/src/components/ScrollToTop.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const ScrollToTop = () => {
4 | const [isVisible, setIsVisible] = useState(false);
5 |
6 | // Show button when page is scrolled down more than 800px
7 | const toggleVisibility = () => {
8 | if (window.pageYOffset > 800) {
9 | setIsVisible(true);
10 | } else {
11 | setIsVisible(false);
12 | }
13 | };
14 |
15 | // Scroll to top smoothly
16 | const scrollToTop = () => {
17 | window.scrollTo({
18 | top: 0,
19 | behavior: "smooth",
20 | });
21 | };
22 |
23 | useEffect(() => {
24 | window.addEventListener("scroll", toggleVisibility);
25 | return () => {
26 | window.removeEventListener("scroll", toggleVisibility);
27 | };
28 | }, []);
29 |
30 | // Arrow up icon as SVG
31 | const ArrowUpIcon = () => (
32 |
38 |
44 |
45 | );
46 |
47 | return (
48 | <>
49 | {isVisible && (
50 |
59 | )}
60 | >
61 | );
62 | };
63 |
64 | export default ScrollToTop;
65 |
--------------------------------------------------------------------------------
/server/checkDB.mjs:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 | import productModel from "./models/productModel.js";
3 | import userModel from "./models/userModel.js";
4 | import orderModel from "./models/orderModel.js";
5 | import "dotenv/config";
6 |
7 | const checkDatabase = async () => {
8 | try {
9 | await mongoose.connect(process.env.MONGO_URI);
10 | console.log("✅ Connected to database");
11 |
12 | // Check products
13 | const products = await productModel.find({});
14 | console.log(`\n📦 Products in database: ${products.length}`);
15 |
16 | if (products.length > 0) {
17 | console.log("\nExisting products:");
18 | products.slice(0, 10).forEach((p, index) => {
19 | console.log(
20 | `${index + 1}. ${p.name} - $${p.price} (Category: ${
21 | p.category
22 | }) - Type: ${p._type || "N/A"}`
23 | );
24 | });
25 | if (products.length > 10) {
26 | console.log(`... and ${products.length - 10} more products`);
27 | }
28 | } else {
29 | console.log("No products found in database");
30 | }
31 |
32 | // Check users
33 | const users = await userModel.find({});
34 | console.log(`\n👥 Users in database: ${users.length}`);
35 | const adminUsers = users.filter((u) => u.role === "admin");
36 | const regularUsers = users.filter((u) => u.role === "user");
37 | console.log(` - Admin users: ${adminUsers.length}`);
38 | console.log(` - Regular users: ${regularUsers.length}`);
39 |
40 | // Check orders
41 | const orders = await orderModel.find({});
42 | console.log(`\n🛍️ Orders in database: ${orders.length}`);
43 |
44 | console.log("\n🎯 Database check completed!");
45 |
46 | await mongoose.disconnect();
47 | process.exit(0);
48 | } catch (error) {
49 | console.error("❌ Error:", error);
50 | process.exit(1);
51 | }
52 | };
53 |
54 | checkDatabase();
55 |
--------------------------------------------------------------------------------
/client/src/components/homeProducts/ProductInfo.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import AddToCartButton from "../AddToCartButton";
3 | import { MdStar } from "react-icons/md";
4 | import PriceContainer from "../PriceContainer";
5 |
6 | const ProductInfo = ({ productInfo }) => {
7 | return (
8 |
9 |
{productInfo.name}
10 |
11 |
12 |
13 | {Array?.from({ length: 5 })?.map((_, index) => {
14 | const filled = index + 1 <= Math.floor(productInfo?.ratings);
15 | const halfFilled =
16 | index + 1 > Math.floor(productInfo?.ratings) &&
17 | index < Math.ceil(productInfo?.ratings);
18 |
19 | return (
20 |
30 | );
31 | })}
32 |
33 |
{`(${productInfo?.ratings?.toFixed(
34 | 1
35 | )} reviews)`}
36 |
37 |
{productInfo.description}
38 |
Be the first to leave a review.
39 |
40 |
41 |
42 |
43 | Categories: {" "}
44 |
45 | {productInfo.category}
46 |
47 |
48 |
49 | );
50 | };
51 |
52 | export default ProductInfo;
53 |
--------------------------------------------------------------------------------
/client/src/components/RootLayout.jsx:
--------------------------------------------------------------------------------
1 | import Header from "./Header";
2 | import { Outlet, ScrollRestoration } from "react-router-dom";
3 | import Footer from "./Footer";
4 | import ScrollToTop from "./ScrollToTop";
5 | import "slick-carousel/slick/slick.css";
6 | import { Provider } from "react-redux";
7 | import { persistor, store } from "../redux/store";
8 | import { PersistGate } from "redux-persist/integration/react";
9 | import { Toaster } from "react-hot-toast";
10 | import MainLoader from "./MainLoader";
11 | import ServicesTag from "./ServicesTag";
12 |
13 | const RootLayout = () => {
14 | return (
15 |
16 | } persistor={persistor}>
17 | {/* Premium Support Badge */}
18 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
47 |
48 |
49 | );
50 | };
51 |
52 | export default RootLayout;
53 |
--------------------------------------------------------------------------------
/admin/src/components/SmallLoader.jsx:
--------------------------------------------------------------------------------
1 | const SmallLoader = () => {
2 | return (
3 |
4 |
10 |
17 |
24 |
31 |
38 |
45 |
52 |
59 |
66 |
67 |
68 | );
69 | };
70 |
71 | export default SmallLoader;
72 |
--------------------------------------------------------------------------------
/admin/src/redux/authSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | user: JSON.parse(localStorage.getItem("user")) || null,
5 | token: localStorage.getItem("token") || null,
6 | isAuthenticated: !!localStorage.getItem("token"),
7 | loading: false,
8 | error: null,
9 | };
10 |
11 | const authSlice = createSlice({
12 | name: "auth",
13 | initialState,
14 | reducers: {
15 | setLoading: (state, action) => {
16 | state.loading = action.payload;
17 | },
18 | setError: (state, action) => {
19 | state.error = action.payload;
20 | },
21 | clearError: (state) => {
22 | state.error = null;
23 | },
24 | loginSuccess: (state, action) => {
25 | state.user = action.payload.user;
26 | state.token = action.payload.token;
27 | state.isAuthenticated = true;
28 | state.loading = false;
29 | state.error = null;
30 | localStorage.setItem("token", action.payload.token);
31 | localStorage.setItem("user", JSON.stringify(action.payload.user));
32 | },
33 | registerSuccess: (state, action) => {
34 | state.user = action.payload.user;
35 | state.token = action.payload.token;
36 | state.isAuthenticated = true;
37 | state.loading = false;
38 | state.error = null;
39 | localStorage.setItem("token", action.payload.token);
40 | localStorage.setItem("user", JSON.stringify(action.payload.user));
41 | },
42 | logout: (state) => {
43 | state.user = null;
44 | state.token = null;
45 | state.isAuthenticated = false;
46 | state.loading = false;
47 | state.error = null;
48 | localStorage.removeItem("token");
49 | localStorage.removeItem("user");
50 | },
51 | setUser: (state, action) => {
52 | state.user = action.payload;
53 | localStorage.setItem("user", JSON.stringify(action.payload));
54 | },
55 | },
56 | });
57 |
58 | export const {
59 | setLoading,
60 | setError,
61 | clearError,
62 | loginSuccess,
63 | registerSuccess,
64 | logout,
65 | setUser,
66 | } = authSlice.actions;
67 |
68 | export default authSlice.reducer;
69 |
--------------------------------------------------------------------------------
/client/src/redux/orebiSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | userInfo: null,
5 | products: [],
6 | orderCount: 0,
7 | };
8 |
9 | export const orebiSlice = createSlice({
10 | name: "orebi",
11 | initialState,
12 | reducers: {
13 | addToCart: (state, action) => {
14 | const item = state.products.find(
15 | (item) => item._id === action.payload._id
16 | );
17 | if (item) {
18 | item.quantity = (item.quantity || 0) + (action.payload.quantity || 1);
19 | } else {
20 | state.products.push({
21 | ...action.payload,
22 | quantity: action.payload.quantity || 1,
23 | });
24 | }
25 | },
26 | increaseQuantity: (state, action) => {
27 | const item = state.products.find((item) => item._id === action.payload);
28 |
29 | if (item) {
30 | item.quantity = (item.quantity || 0) + 1;
31 | }
32 | },
33 | decreaseQuantity: (state, action) => {
34 | const item = state.products.find((item) => item._id === action.payload);
35 |
36 | if (item) {
37 | const currentQuantity = item.quantity || 1;
38 | if (currentQuantity === 1) {
39 | item.quantity = 1;
40 | } else {
41 | item.quantity = currentQuantity - 1;
42 | }
43 | }
44 | },
45 | deleteItem: (state, action) => {
46 | state.products = state.products.filter(
47 | (item) => item._id !== action.payload
48 | );
49 | },
50 | resetCart: (state) => {
51 | state.products = [];
52 | },
53 | addUser: (state, action) => {
54 | state.userInfo = action.payload;
55 | },
56 | removeUser: (state) => {
57 | state.userInfo = null;
58 | },
59 | setOrderCount: (state, action) => {
60 | state.orderCount = action.payload;
61 | },
62 | resetOrderCount: (state) => {
63 | state.orderCount = 0;
64 | },
65 | },
66 | });
67 |
68 | export const {
69 | addToCart,
70 | increaseQuantity,
71 | decreaseQuantity,
72 | deleteItem,
73 | resetCart,
74 | addUser,
75 | removeUser,
76 | setOrderCount,
77 | resetOrderCount,
78 | } = orebiSlice.actions;
79 | export default orebiSlice.reducer;
80 |
--------------------------------------------------------------------------------
/client/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useCallback } from "react";
2 | import Banner from "./components/Banner";
3 | import Container from "./components/Container";
4 | import BestSellers from "./components/homeProducts/BestSellers";
5 | import NewArrivals from "./components/homeProducts/NewArrivals";
6 | import ProductOfTheYear from "./components/homeProducts/ProductOfTheYear";
7 | import SpecialOffers from "./components/homeProducts/SpecialOffers";
8 | import { jwtDecode } from "jwt-decode";
9 | import { useDispatch } from "react-redux";
10 | import {
11 | addUser,
12 | removeUser,
13 | setOrderCount,
14 | resetOrderCount,
15 | } from "./redux/orebiSlice";
16 | import { serverUrl } from "../config";
17 |
18 | function App() {
19 | const token = localStorage.getItem("token");
20 | const dispatch = useDispatch();
21 |
22 | // Function to fetch user orders and update count
23 | const fetchUserOrderCount = useCallback(
24 | async (token) => {
25 | try {
26 | const response = await fetch(`${serverUrl}/api/order/my-orders`, {
27 | headers: {
28 | Authorization: `Bearer ${token}`,
29 | },
30 | });
31 |
32 | const data = await response.json();
33 | if (data.success) {
34 | dispatch(setOrderCount(data.orders.length));
35 | }
36 | } catch (error) {
37 | console.error("Error fetching order count:", error);
38 | // Don't show error to user as this is not critical
39 | }
40 | },
41 | [dispatch]
42 | );
43 |
44 | useEffect(() => {
45 | if (token) {
46 | try {
47 | const decodedToken = jwtDecode(token);
48 | dispatch(addUser(decodedToken));
49 | // Fetch order count for authenticated users
50 | fetchUserOrderCount(token);
51 | } catch (error) {
52 | console.error("Invalid token", error);
53 | localStorage.removeItem("token");
54 | dispatch(resetOrderCount());
55 | }
56 | } else {
57 | dispatch(removeUser());
58 | dispatch(resetOrderCount());
59 | }
60 | }, [token, dispatch, fetchUserOrderCount]);
61 | return (
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | );
72 | }
73 |
74 | export default App;
75 |
--------------------------------------------------------------------------------
/server/config/cloudinary.js:
--------------------------------------------------------------------------------
1 | import { v2 as cloudinary } from "cloudinary";
2 |
3 | const connectCloudinary = async () => {
4 | cloudinary.config({
5 | cloud_name: process.env.CLOUDINARY_NAME,
6 | api_key: process.env.CLOUDINARY_API_KEY,
7 | api_secret: process.env.CLOUDINARY_SECRET_KEY,
8 | });
9 | };
10 |
11 | // Helper function to extract public_id from Cloudinary URL
12 | const getCloudinaryPublicId = (imageUrl) => {
13 | if (!imageUrl) return null;
14 |
15 | try {
16 | // Extract public_id from Cloudinary URL
17 | const urlParts = imageUrl.split("/");
18 | const versionIndex = urlParts.findIndex((part) => part.startsWith("v"));
19 |
20 | if (versionIndex !== -1 && versionIndex < urlParts.length - 1) {
21 | // Remove file extension from the last part
22 | const filename = urlParts[urlParts.length - 1];
23 | const filenameWithoutExt = filename.split(".")[0];
24 |
25 | // Reconstruct the public_id with folder structure
26 | const folderParts = urlParts.slice(versionIndex + 1, -1);
27 | return folderParts.length > 0
28 | ? `${folderParts.join("/")}/${filenameWithoutExt}`
29 | : filenameWithoutExt;
30 | }
31 |
32 | // Fallback: assume last part is filename
33 | const filename = urlParts[urlParts.length - 1];
34 | return filename.split(".")[0];
35 | } catch (error) {
36 | console.error("Error extracting public_id:", error);
37 | return null;
38 | }
39 | };
40 |
41 | // Helper function to delete image from Cloudinary
42 | const deleteCloudinaryImage = async (imageUrl) => {
43 | try {
44 | if (!imageUrl) return { success: false, message: "No image URL provided" };
45 |
46 | const publicId = getCloudinaryPublicId(imageUrl);
47 | if (!publicId) {
48 | return {
49 | success: false,
50 | message: "Could not extract public_id from URL",
51 | };
52 | }
53 |
54 | const result = await cloudinary.uploader.destroy(publicId);
55 | return {
56 | success: result.result === "ok",
57 | message:
58 | result.result === "ok"
59 | ? "Image deleted successfully"
60 | : "Image deletion failed",
61 | result,
62 | };
63 | } catch (error) {
64 | console.error("Error deleting image from Cloudinary:", error);
65 | return { success: false, message: error.message };
66 | }
67 | };
68 |
69 | export default connectCloudinary;
70 | export { cloudinary, getCloudinaryPublicId, deleteCloudinaryImage };
71 |
--------------------------------------------------------------------------------
/client/src/components/PriceContainer.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { useSelector } from "react-redux";
3 | import PropTypes from "prop-types";
4 | import PriceFormat from "./PriceFormat";
5 | import { twMerge } from "tailwind-merge";
6 |
7 | const PriceContainer = ({ item, className }) => {
8 | const { products } = useSelector((state) => state.orebiReducer);
9 | const [existingProduct, setExistingProduct] = useState(null);
10 | useEffect(() => {
11 | const availableItem = products.find(
12 | (product) => product?._id === item?._id
13 | );
14 |
15 | setExistingProduct(availableItem || null);
16 | }, [products, item]);
17 | const regularPrice = () => {
18 | if (existingProduct) {
19 | const price = existingProduct?.price || 0;
20 | const discountPercentage = existingProduct?.discountedPercentage || 0;
21 | const quantity = existingProduct?.quantity || 1;
22 | return (price + (discountPercentage * price) / 100) * quantity;
23 | } else {
24 | const price = item?.price || 0;
25 | const discountPercentage = item?.discountedPercentage || 0;
26 | return price + (discountPercentage * price) / 100;
27 | }
28 | };
29 |
30 | const discountedPrice = () => {
31 | if (existingProduct) {
32 | const price = item?.price || 0;
33 | const quantity = existingProduct?.quantity || 1;
34 | return price * quantity;
35 | } else {
36 | return item?.price || 0;
37 | }
38 | };
39 | return (
40 |
43 | {item?.offer && item?.discountedPercentage ? (
44 | <>
45 |
49 |
53 | >
54 | ) : (
55 |
59 | )}
60 |
61 | );
62 | };
63 |
64 | PriceContainer.propTypes = {
65 | item: PropTypes.shape({
66 | _id: PropTypes.string,
67 | price: PropTypes.number,
68 | offer: PropTypes.bool,
69 | discountedPercentage: PropTypes.number,
70 | }).isRequired,
71 | className: PropTypes.string,
72 | };
73 |
74 | export default PriceContainer;
75 |
--------------------------------------------------------------------------------
/server/models/orderModel.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const orderSchema = new mongoose.Schema({
4 | userId: {
5 | type: mongoose.Schema.Types.ObjectId,
6 | ref: "user",
7 | required: true,
8 | },
9 | items: [
10 | {
11 | productId: {
12 | type: mongoose.Schema.Types.ObjectId,
13 | ref: "product",
14 | required: true,
15 | },
16 | name: {
17 | type: String,
18 | required: true,
19 | },
20 | price: {
21 | type: Number,
22 | required: true,
23 | },
24 | quantity: {
25 | type: Number,
26 | required: true,
27 | default: 1,
28 | },
29 | image: {
30 | type: String,
31 | },
32 | },
33 | ],
34 | amount: {
35 | type: Number,
36 | required: true,
37 | },
38 | address: {
39 | firstName: {
40 | type: String,
41 | required: true,
42 | },
43 | lastName: {
44 | type: String,
45 | required: true,
46 | },
47 | email: {
48 | type: String,
49 | required: true,
50 | },
51 | street: {
52 | type: String,
53 | required: true,
54 | },
55 | city: {
56 | type: String,
57 | required: true,
58 | },
59 | state: {
60 | type: String,
61 | required: true,
62 | },
63 | zipcode: {
64 | type: String,
65 | required: true,
66 | },
67 | country: {
68 | type: String,
69 | required: true,
70 | },
71 | phone: {
72 | type: String,
73 | required: true,
74 | },
75 | },
76 | status: {
77 | type: String,
78 | enum: ["pending", "confirmed", "shipped", "delivered", "cancelled"],
79 | default: "pending",
80 | },
81 | paymentMethod: {
82 | type: String,
83 | enum: ["cod", "stripe", "paypal"],
84 | default: "cod",
85 | },
86 | paymentStatus: {
87 | type: String,
88 | enum: ["pending", "paid", "failed", "refunded"],
89 | default: "pending",
90 | },
91 | date: {
92 | type: Date,
93 | default: Date.now,
94 | },
95 | updatedAt: {
96 | type: Date,
97 | default: Date.now,
98 | },
99 | });
100 |
101 | orderSchema.pre("save", function (next) {
102 | this.updatedAt = Date.now();
103 | next();
104 | });
105 |
106 | const orderModel =
107 | mongoose.models.order || mongoose.model("order", orderSchema);
108 |
109 | export default orderModel;
110 |
--------------------------------------------------------------------------------
/server/index.mjs:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | const app = express();
3 | import "dotenv/config";
4 | import cors from "cors";
5 | import { fileURLToPath } from "url";
6 | import path from "path";
7 | import { readdirSync } from "fs";
8 | import dbConnect from "./config/mongodb.js";
9 | import connectCloudinary from "./config/cloudinary.js";
10 |
11 | const port = process.env.PORT;
12 |
13 | const allowedOrigins = [
14 | process.env.ADMIN_URL,
15 | process.env.CLIENT_URL,
16 | // Add production URLs
17 | // Add localhost for development
18 | "http://localhost:5174",
19 | "http://localhost:5173",
20 | "http://localhost:8081", // iOS simulator
21 | "http://10.0.2.2:8081", // Android emulator
22 | "http://10.0.2.2:8000", // Android emulator direct access
23 | ].filter(Boolean); // Remove any undefined values
24 |
25 | // CORS configuration using config system
26 | console.log("Allowed CORS Origins:", allowedOrigins);
27 |
28 | app.use(
29 | cors({
30 | origin: function (origin, callback) {
31 | console.log("CORS request from origin:", origin);
32 |
33 | // Allow requests with no origin (like mobile apps or curl requests)
34 | if (!origin) return callback(null, true);
35 |
36 | // In development, allow all origins for easier testing
37 | if (process.env.NODE_ENV === "development") {
38 | console.log("Development mode: allowing all origins");
39 | return callback(null, true);
40 | }
41 |
42 | if (allowedOrigins.indexOf(origin) !== -1) {
43 | console.log("Origin allowed:", origin);
44 | callback(null, true);
45 | } else {
46 | console.log("Origin blocked:", origin);
47 | callback(new Error("Not allowed by CORS"));
48 | }
49 | },
50 | credentials: true,
51 | methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
52 | allowedHeaders: ["Content-Type", "Authorization"],
53 | })
54 | );
55 | app.use(express.json());
56 |
57 | dbConnect();
58 | connectCloudinary();
59 |
60 | const __filename = fileURLToPath(import.meta.url);
61 | const __dirname = path.dirname(__filename);
62 |
63 | const routesPath = path.resolve(__dirname, "./routes");
64 | const routeFiles = readdirSync(routesPath);
65 | routeFiles.map(async (file) => {
66 | const routeModule = await import(`./routes/${file}`);
67 | app.use("/", routeModule.default);
68 | });
69 |
70 | app.get("/", (req, res) => {
71 | res.send("You should not be here");
72 | });
73 |
74 | app.listen(port, () => {
75 | console.log(`Server is running on ${port}`);
76 | });
77 |
--------------------------------------------------------------------------------
/admin/src/components/Loader.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Loader = () => {
4 | return (
5 |
6 |
7 |
8 |
14 |
21 |
28 |
35 |
42 |
49 |
56 |
63 |
70 |
71 |
72 |
73 |
74 | );
75 | };
76 |
77 | export default Loader;
78 |
--------------------------------------------------------------------------------
/client/src/components/homeProducts/ProductsOnSale.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import PriceFormat from "../PriceFormat";
3 | import { useNavigate } from "react-router-dom";
4 | import { getData } from "../../helpers";
5 |
6 | const ProductsOnSale = () => {
7 | const [products, setProducts] = useState([]);
8 | const navigate = useNavigate();
9 |
10 | useEffect(() => {
11 | const fetchOnSaleProducts = async () => {
12 | try {
13 | // Fetch products that are on sale from the database
14 | const response = await getData("/api/products");
15 | if (response?.success) {
16 | // Filter products that are on sale
17 | const onSaleProducts = response.data.filter(
18 | (item) => item?.onSale || item?.offer
19 | );
20 | setProducts(onSaleProducts.slice(0, 6)); // Limit to 6 products
21 | }
22 | } catch (error) {
23 | console.error("Error fetching on sale products:", error);
24 | }
25 | };
26 |
27 | fetchOnSaleProducts();
28 | }, []);
29 |
30 | if (products.length === 0) {
31 | return (
32 |
33 |
34 | Products on sale
35 |
36 |
No products on sale at the moment.
37 |
38 | );
39 | }
40 |
41 | return (
42 |
43 |
44 | Products on sale
45 |
46 |
47 | {products.map((item) => (
48 |
50 | navigate(`/product/${item?._id}`, {
51 | state: {
52 | item: item,
53 | },
54 | })
55 | }
56 | key={item._id}
57 | className="flex items-center gap-2 border-[1px] border-primary/20 py-2 rounded-md hover:border-primary/80 cursor-pointer duration-300"
58 | >
59 |
64 |
65 |
66 |
{item.name}
67 |
68 |
69 |
70 | ))}
71 |
72 |
73 | );
74 | };
75 |
76 | export default ProductsOnSale;
77 |
--------------------------------------------------------------------------------
/client/src/components/products/ProductBanner.jsx:
--------------------------------------------------------------------------------
1 | import { GoTriangleDown } from "react-icons/go";
2 |
3 | const ProductBanner = ({ itemsPerPageFromBanner, getTypes }) => {
4 | const sortArr = [
5 | { title: "Select option", _type: "" },
6 | { title: "New Arrivals", _type: "new_arrivals" },
7 | { title: "Best Sellers", _type: "best_sellers" },
8 | { title: "Offers", _type: "offers" },
9 | ];
10 |
11 | const handleSortChange = (e) => {
12 | const selectedType = e.target.value;
13 | getTypes(selectedType);
14 | };
15 |
16 | return (
17 |
18 |
19 |
20 | Sort by:
21 |
26 | {sortArr?.map((item) => (
27 |
28 | {item?.title}
29 |
30 | ))}
31 | {/* Best Sellers
32 | New Arrival
33 | Featured
34 | Final Offer */}
35 |
36 |
37 |
38 |
39 |
40 |
41 | Show:
42 | itemsPerPageFromBanner(+e.target.value)}
44 | id="countries"
45 | className="w-16 md:w-20 border-[1px] border-gray-200 py-1 px-4 cursor-pointer text-primeColor text-base block dark:placeholder-gray-400 appearance-none focus-within:outline-none focus-visible:border-primeColor"
46 | >
47 | 12
48 | 24
49 | 36
50 | 48
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default ProductBanner;
62 |
--------------------------------------------------------------------------------
/server/routes/userRoute.mjs:
--------------------------------------------------------------------------------
1 | import { Router } from "express";
2 | import {
3 | adminLogin,
4 | getUsers,
5 | removeUser,
6 | updateUser,
7 | userLogin,
8 | userRegister,
9 | getUserProfile,
10 | updateUserProfile,
11 | addToCart,
12 | updateCart,
13 | getUserCart,
14 | clearCart,
15 | createAdmin,
16 | addAddress,
17 | updateAddress,
18 | deleteAddress,
19 | setDefaultAddress,
20 | getUserAddresses,
21 | uploadUserAvatar,
22 | } from "../controllers/userController.mjs";
23 | import adminAuth from "../middleware/adminAuth.js";
24 | import userAuth from "../middleware/userAuth.js";
25 | import { avatarUpload } from "../middleware/avatarUpload.mjs";
26 |
27 | const router = Router();
28 |
29 | const routeValue = "/api/user/";
30 |
31 | // Public routes
32 | router.post(`${routeValue}register`, userRegister);
33 | router.post(`${routeValue}login`, userLogin);
34 | router.post(`${routeValue}admin`, adminLogin);
35 |
36 | // User-protected routes
37 | router.get(`${routeValue}profile`, userAuth, getUserProfile);
38 | router.put(`${routeValue}profile`, userAuth, updateUserProfile);
39 | router.post(`${routeValue}cart/add`, userAuth, addToCart);
40 | router.put(`${routeValue}cart/update`, userAuth, updateCart);
41 | router.get(`${routeValue}cart`, userAuth, getUserCart);
42 | router.delete(`${routeValue}cart/clear`, userAuth, clearCart);
43 |
44 | // User address management routes
45 | router.get(`${routeValue}addresses`, userAuth, getUserAddresses);
46 | router.post(`${routeValue}addresses`, userAuth, addAddress);
47 | router.put(`${routeValue}addresses/:addressId`, userAuth, updateAddress);
48 | router.delete(`${routeValue}addresses/:addressId`, userAuth, deleteAddress);
49 | router.put(
50 | `${routeValue}addresses/:addressId/default`,
51 | userAuth,
52 | setDefaultAddress
53 | );
54 |
55 | // Avatar upload route (admin only)
56 | router.post(
57 | `${routeValue}upload-avatar`,
58 | adminAuth,
59 | avatarUpload.single("avatar"),
60 | uploadUserAvatar
61 | );
62 |
63 | // Address management routes (admin only)
64 | router.get(`${routeValue}:userId/addresses`, adminAuth, getUserAddresses);
65 | router.post(`${routeValue}:userId/addresses`, adminAuth, addAddress);
66 | router.put(
67 | `${routeValue}:userId/addresses/:addressId`,
68 | adminAuth,
69 | updateAddress
70 | );
71 | router.delete(
72 | `${routeValue}:userId/addresses/:addressId`,
73 | adminAuth,
74 | deleteAddress
75 | );
76 | router.put(
77 | `${routeValue}:userId/addresses/:addressId/default`,
78 | adminAuth,
79 | setDefaultAddress
80 | );
81 |
82 | // Admin-protected routes
83 | router.get(`${routeValue}users`, adminAuth, getUsers);
84 | router.post(`${routeValue}remove`, adminAuth, removeUser);
85 | router.put(`${routeValue}update/:id`, adminAuth, updateUser);
86 | router.post(`${routeValue}create-admin`, adminAuth, createAdmin);
87 |
88 | export default router;
89 |
--------------------------------------------------------------------------------
/client/src/helpers/stockManager.js:
--------------------------------------------------------------------------------
1 | // Stock management helper functions
2 |
3 | /**
4 | * Process checkout and update product stock
5 | * @param {Array} cartItems - Array of {productId, quantity}
6 | * @param {string} serverUrl - Backend server URL
7 | * @returns {Promise} - Checkout response
8 | */
9 | export const processCheckout = async (cartItems, serverUrl) => {
10 | try {
11 | const response = await fetch(`${serverUrl}/checkout`, {
12 | method: "POST",
13 | headers: {
14 | "Content-Type": "application/json",
15 | },
16 | body: JSON.stringify({
17 | items: cartItems,
18 | }),
19 | });
20 |
21 | const data = await response.json();
22 | return data;
23 | } catch (error) {
24 | console.error("Checkout error:", error);
25 | throw error;
26 | }
27 | };
28 |
29 | /**
30 | * Update stock for a single product
31 | * @param {string} productId - Product ID
32 | * @param {number} quantity - Quantity to reduce
33 | * @param {string} serverUrl - Backend server URL
34 | * @returns {Promise} - Stock update response
35 | */
36 | export const updateProductStock = async (productId, quantity, serverUrl) => {
37 | try {
38 | const response = await fetch(`${serverUrl}/api/product/update-stock`, {
39 | method: "POST",
40 | headers: {
41 | "Content-Type": "application/json",
42 | },
43 | body: JSON.stringify({
44 | productId,
45 | quantity,
46 | }),
47 | });
48 |
49 | const data = await response.json();
50 | return data;
51 | } catch (error) {
52 | console.error("Stock update error:", error);
53 | throw error;
54 | }
55 | };
56 |
57 | /**
58 | * Check if product has sufficient stock
59 | * @param {Object} product - Product object
60 | * @param {number} requestedQuantity - Requested quantity
61 | * @returns {boolean} - Whether stock is sufficient
62 | */
63 | export const hasValidStock = (product, requestedQuantity) => {
64 | return product && product.stock >= requestedQuantity && product.isAvailable;
65 | };
66 |
67 | /**
68 | * Get stock status text
69 | * @param {Object} product - Product object
70 | * @returns {string} - Stock status text
71 | */
72 | export const getStockStatus = (product) => {
73 | if (!product) return "Product not found";
74 |
75 | if (!product.isAvailable) return "Out of Stock";
76 |
77 | if (product.stock === 0) return "Out of Stock";
78 |
79 | if (product.stock <= 5) return `Only ${product.stock} left`;
80 |
81 | if (product.stock <= 10) return `${product.stock} in stock`;
82 |
83 | return "In Stock";
84 | };
85 |
86 | /**
87 | * Calculate discounted price
88 | * @param {number} price - Original price
89 | * @param {number} discountPercentage - Discount percentage (default 10)
90 | * @returns {number} - Discounted price
91 | */
92 | export const calculateDiscountedPrice = (price, discountPercentage = 10) => {
93 | const discountAmount = (price * discountPercentage) / 100;
94 | return price - discountAmount;
95 | };
96 |
--------------------------------------------------------------------------------
/client/public/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | https://orebiclient.reactbd.com/
6 | 2025-06-18
7 | daily
8 | 1.0
9 |
10 |
11 |
12 | https://orebiclient.reactbd.com/shop
13 | 2025-06-18
14 | daily
15 | 0.9
16 |
17 |
18 |
19 | https://orebiclient.reactbd.com/about
20 | 2025-06-18
21 | monthly
22 | 0.8
23 |
24 |
25 |
26 | https://orebiclient.reactbd.com/contact
27 | 2025-06-18
28 | monthly
29 | 0.8
30 |
31 |
32 |
33 | https://orebiclient.reactbd.com/offers
34 | 2025-06-18
35 | daily
36 | 0.9
37 |
38 |
39 |
40 | https://orebiclient.reactbd.com/orders
41 | 2025-06-18
42 | daily
43 | 0.7
44 |
45 |
46 |
47 | https://orebiclient.reactbd.com/cart
48 | 2025-06-18
49 | daily
50 | 0.7
51 |
52 |
53 |
54 | https://orebiclient.reactbd.com/product
55 | 2025-06-18
56 | weekly
57 | 0.8
58 |
59 |
60 |
61 | https://orebiclient.reactbd.com/signup
62 | 2025-06-18
63 | monthly
64 | 0.6
65 |
66 |
67 |
68 | https://orebiclient.reactbd.com/signin
69 | 2025-06-18
70 | monthly
71 | 0.6
72 |
73 |
74 |
75 | https://orebiclient.reactbd.com/profile
76 | 2025-06-18
77 | weekly
78 | 0.7
79 |
80 |
81 |
82 | https://orebiclient.reactbd.com/product/123
83 | 2025-06-18
84 | weekly
85 | 0.8
86 |
87 |
88 | https://orebiclient.reactbd.com/product/456
89 | 2025-06-18
90 | weekly
91 | 0.8
92 |
93 |
--------------------------------------------------------------------------------
/client/src/components/AddToCartButton.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import toast from "react-hot-toast";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import PropTypes from "prop-types";
5 | import {
6 | addToCart,
7 | decreaseQuantity,
8 | increaseQuantity,
9 | } from "../redux/orebiSlice";
10 | import { FaMinus, FaPlus } from "react-icons/fa";
11 | import { cn } from "./ui/cn";
12 |
13 | const AddToCartButton = ({ item, className }) => {
14 | const dispatch = useDispatch();
15 | const { products } = useSelector((state) => state.orebiReducer);
16 | const [existingProduct, setExistingProduct] = useState(null);
17 | useEffect(() => {
18 | const availableItem = products.find(
19 | (product) => product?._id === item?._id
20 | );
21 |
22 | setExistingProduct(availableItem || null);
23 | }, [products, item]);
24 |
25 | const handleAddToCart = () => {
26 | dispatch(addToCart(item));
27 | toast.success(`${item?.name.substring(0, 10)}... is added successfully!`);
28 | };
29 |
30 | return (
31 | <>
32 | {existingProduct ? (
33 |
39 |
{
42 | dispatch(decreaseQuantity(item?._id));
43 | toast.success("Quantity decreased successfully!");
44 | }}
45 | className="border border-gray-300 text-gray-700 p-2 hover:border-black hover:text-black rounded-md text-sm transition-all duration-200 cursor-pointer disabled:text-gray-300 disabled:border-gray-200 disabled:hover:border-gray-200 disabled:hover:text-gray-300"
46 | >
47 |
48 |
49 |
50 | {existingProduct?.quantity || 0}
51 |
52 |
{
54 | dispatch(increaseQuantity(item?._id));
55 | toast.success("Quantity increased successfully!");
56 | }}
57 | className="border border-gray-300 text-gray-700 p-2 hover:border-black hover:text-black rounded-md text-sm transition-all duration-200 cursor-pointer"
58 | >
59 |
60 |
61 |
62 | ) : (
63 |
67 | Add to cart
68 |
69 | )}
70 | >
71 | );
72 | };
73 |
74 | AddToCartButton.propTypes = {
75 | item: PropTypes.shape({
76 | _id: PropTypes.string,
77 | name: PropTypes.string,
78 | }).isRequired,
79 | className: PropTypes.string,
80 | };
81 |
82 | export default AddToCartButton;
83 |
--------------------------------------------------------------------------------
/server/controllers/checkoutController.mjs:
--------------------------------------------------------------------------------
1 | import productModel from "../models/productModel.js";
2 |
3 | // Process checkout and update stock
4 | const processCheckout = async (req, res) => {
5 | try {
6 | const { items } = req.body; // Array of {productId, quantity}
7 |
8 | if (!items || !Array.isArray(items) || items.length === 0) {
9 | return res.status(400).json({
10 | success: false,
11 | message: "Cart items are required",
12 | });
13 | }
14 |
15 | const updatePromises = [];
16 | const stockCheckErrors = [];
17 |
18 | // First, check stock availability for all items
19 | for (const item of items) {
20 | const { productId, quantity } = item;
21 |
22 | if (!productId || !quantity || quantity <= 0) {
23 | stockCheckErrors.push("Invalid product or quantity");
24 | continue;
25 | }
26 |
27 | const product = await productModel.findById(productId);
28 |
29 | if (!product) {
30 | stockCheckErrors.push(`Product not found: ${productId}`);
31 | continue;
32 | }
33 |
34 | if (product.stock < quantity) {
35 | stockCheckErrors.push(
36 | `Insufficient stock for ${product.name}. Available: ${product.stock}, Requested: ${quantity}`
37 | );
38 | continue;
39 | }
40 | }
41 |
42 | if (stockCheckErrors.length > 0) {
43 | return res.status(400).json({
44 | success: false,
45 | message: "Stock validation failed",
46 | errors: stockCheckErrors,
47 | });
48 | }
49 |
50 | // If all checks pass, update stock for all items
51 | for (const item of items) {
52 | const { productId, quantity } = item;
53 |
54 | const updatePromise = productModel
55 | .findByIdAndUpdate(
56 | productId,
57 | {
58 | $inc: {
59 | stock: -quantity,
60 | soldQuantity: quantity,
61 | },
62 | },
63 | { new: true }
64 | )
65 | .then(async (product) => {
66 | // If stock becomes 0, mark as unavailable
67 | if (product.stock === 0) {
68 | await productModel.findByIdAndUpdate(productId, {
69 | isAvailable: false,
70 | });
71 | }
72 | return product;
73 | });
74 |
75 | updatePromises.push(updatePromise);
76 | }
77 |
78 | const updatedProducts = await Promise.all(updatePromises);
79 |
80 | res.json({
81 | success: true,
82 | message: "Checkout processed successfully",
83 | updatedProducts: updatedProducts.map((product) => ({
84 | _id: product._id,
85 | name: product.name,
86 | stock: product.stock,
87 | soldQuantity: product.soldQuantity,
88 | isAvailable: product.isAvailable,
89 | })),
90 | });
91 | } catch (error) {
92 | console.log("Checkout processing error:", error);
93 | res.status(500).json({
94 | success: false,
95 | message: "Error processing checkout",
96 | error: error.message,
97 | });
98 | }
99 | };
100 |
101 | export { processCheckout };
102 |
--------------------------------------------------------------------------------
/client/src/main.jsx:
--------------------------------------------------------------------------------
1 | import { createRoot } from "react-dom/client";
2 | import { createBrowserRouter, RouterProvider } from "react-router-dom";
3 | import App from "./App.jsx";
4 | import "./styles/index.css";
5 | import SignIn from "./pages/SignIn.jsx";
6 | import SignUp from "./pages/SignUp.jsx";
7 | import About from "./pages/About.jsx";
8 | import Cart from "./pages/Cart.jsx";
9 | import Contact from "./pages/Contact.jsx";
10 | import Offers from "./pages/Offers.jsx";
11 | import Order from "./pages/Order.jsx";
12 | import Product from "./pages/Product.jsx";
13 | import Shop from "./pages/Shop.jsx";
14 | import SingleProduct from "./pages/SingleProduct.jsx";
15 | import RootLayout from "./components/RootLayout.jsx";
16 | import Profile from "./pages/Profile.jsx";
17 | import NotFound from "./pages/NotFound.jsx";
18 | import Wishlist from "./pages/Wishlist.jsx";
19 | import Checkout from "./pages/Checkout.jsx";
20 | import PaymentSuccess from "./pages/PaymentSuccess.jsx";
21 | import FAQ from "./pages/FAQ.jsx";
22 | import Blog from "./pages/Blog.jsx";
23 |
24 | const router = createBrowserRouter(
25 | [
26 | {
27 | path: "/",
28 | element: ,
29 | children: [
30 | {
31 | path: "/",
32 | element: ,
33 | },
34 | {
35 | path: "/about",
36 | element: ,
37 | },
38 | {
39 | path: "/cart",
40 | element: ,
41 | },
42 | {
43 | path: "/contact",
44 | element: ,
45 | },
46 | {
47 | path: "/faq",
48 | element: ,
49 | },
50 | {
51 | path: "/blog",
52 | element: ,
53 | },
54 | {
55 | path: "/offers",
56 | element: ,
57 | },
58 | {
59 | path: "/orders",
60 | element: ,
61 | },
62 | {
63 | path: "/Product",
64 | element: ,
65 | },
66 | {
67 | path: "/shop",
68 | element: ,
69 | },
70 | {
71 | path: "/signin",
72 | element: ,
73 | },
74 | {
75 | path: "/signup",
76 | element: ,
77 | },
78 | {
79 | path: "/profile",
80 | element: ,
81 | },
82 | {
83 | path: "/wishlist",
84 | element: ,
85 | },
86 | {
87 | path: "/checkout/:orderId",
88 | element: ,
89 | },
90 | {
91 | path: "/payment-success",
92 | element: ,
93 | },
94 | {
95 | path: "/payment/success",
96 | element: ,
97 | },
98 | {
99 | path: "/product/:id",
100 | element: ,
101 | },
102 | {
103 | path: "*",
104 | element: ,
105 | },
106 | ],
107 | },
108 | ],
109 | {
110 | future: {
111 | v7_startTransition: true,
112 | },
113 | }
114 | );
115 |
116 | createRoot(document.getElementById("root")).render(
117 |
118 | );
119 |
--------------------------------------------------------------------------------
/client/src/components/PremiumModal.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import { FaLock, FaGift, FaTimes } from "react-icons/fa";
3 |
4 | const PremiumModal = ({
5 | isOpen,
6 | onClose,
7 | title = "Premium Feature",
8 | description = "This feature is available in the premium version.",
9 | }) => {
10 | if (!isOpen) return null;
11 |
12 | return (
13 |
20 | e.stopPropagation()}
26 | >
27 | {/* Header */}
28 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
39 |
40 | {/* Content */}
41 |
42 |
🔒 {title}
43 |
{description}
44 |
45 | {/* Premium Access Info */}
46 |
47 |
48 | 💎 Premium Access Required
49 |
50 |
51 | Get instant access to the complete source code and unlock all
52 | functionality.
53 |
54 |
55 | ⚡ One-time payment • Lifetime access
56 |
57 |
58 |
59 | {/* CTA Buttons */}
60 |
77 |
78 |
79 |
80 | );
81 | };
82 |
83 | export default PremiumModal;
84 |
--------------------------------------------------------------------------------
/client/src/components/products/PaginationProductList.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import PropTypes from "prop-types";
3 | import Pagination from "./Pagination";
4 | import ProductCard from "../ProductCard";
5 |
6 | const PaginationProductList = ({
7 | products = [],
8 | currentPage = 1,
9 | itemsPerPage = 12,
10 | onPageChange,
11 | viewMode = "grid",
12 | }) => {
13 | const [paginatedProducts, setPaginatedProducts] = useState([]);
14 |
15 | useEffect(() => {
16 | const startIndex = (currentPage - 1) * itemsPerPage;
17 | const endIndex = startIndex + itemsPerPage;
18 | setPaginatedProducts(products.slice(startIndex, endIndex));
19 | }, [products, currentPage, itemsPerPage]);
20 |
21 | const totalPages = Math.ceil(products.length / itemsPerPage);
22 |
23 | if (products.length === 0) {
24 | return (
25 |
26 |
27 |
42 |
43 | No products found
44 |
45 |
46 | We couldn't find any products matching your criteria. Try
47 | adjusting your filters or search terms.
48 |
49 |
50 |
51 | );
52 | }
53 |
54 | return (
55 |
56 | {/* Products Grid/List */}
57 |
64 | {paginatedProducts.map((product) => (
65 |
75 | ))}
76 |
77 |
78 | {/* Pagination */}
79 | {totalPages > 1 && (
80 |
89 | )}
90 |
91 | );
92 | };
93 | PaginationProductList.propTypes = {
94 | products: PropTypes.array.isRequired,
95 | currentPage: PropTypes.number,
96 | itemsPerPage: PropTypes.number,
97 | onPageChange: PropTypes.func,
98 | viewMode: PropTypes.string,
99 | };
100 |
101 | export default PaginationProductList;
102 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Orebi Shopping | Your Ultimate E-Commerce Destination
14 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
32 |
33 |
34 |
35 |
36 |
40 |
44 |
48 |
49 |
50 |
51 |
52 |
56 |
60 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/admin/src/pages/Analytics.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | FaChartLine,
3 | FaUsers,
4 | FaShoppingCart,
5 | FaDollarSign,
6 | } from "react-icons/fa";
7 | import { MdTrendingUp, MdTrendingDown } from "react-icons/md";
8 |
9 | const Analytics = () => {
10 | const stats = [
11 | {
12 | title: "Total Revenue",
13 | value: "$45,230",
14 | change: "+12.5%",
15 | trend: "up",
16 | icon: ,
17 | color: "green",
18 | },
19 | {
20 | title: "Total Orders",
21 | value: "1,234",
22 | change: "+8.2%",
23 | trend: "up",
24 | icon: ,
25 | color: "blue",
26 | },
27 | {
28 | title: "Total Users",
29 | value: "892",
30 | change: "+15.3%",
31 | trend: "up",
32 | icon: ,
33 | color: "purple",
34 | },
35 | {
36 | title: "Conversion Rate",
37 | value: "3.24%",
38 | change: "-2.1%",
39 | trend: "down",
40 | icon: ,
41 | color: "orange",
42 | },
43 | ];
44 |
45 | return (
46 |
47 |
48 |
49 | Analytics Dashboard
50 |
51 |
52 | Track your business performance and insights
53 |
54 |
55 |
56 | {/* Stats Grid */}
57 |
58 | {stats.map((stat, index) => (
59 |
63 |
64 |
67 | {stat.icon}
68 |
69 |
74 | {stat.trend === "up" ? : }
75 | {stat.change}
76 |
77 |
78 |
79 | {stat.value}
80 |
81 |
{stat.title}
82 |
83 | ))}
84 |
85 |
86 | {/* Charts Section */}
87 |
88 |
89 |
90 | Revenue Chart
91 |
92 |
93 |
Chart will be integrated here
94 |
95 |
96 |
97 |
98 |
99 | Order Trends
100 |
101 |
102 |
Chart will be integrated here
103 |
104 |
105 |
106 |
107 | );
108 | };
109 |
110 | export default Analytics;
111 |
--------------------------------------------------------------------------------
/admin/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { Route, Routes } from "react-router-dom";
2 | import { useSelector } from "react-redux";
3 | import Navbar from "./components/Navbar";
4 | import Sidebar from "./components/Sidebar";
5 | import Add from "./pages/Add";
6 | import List from "./pages/List";
7 | import Orders from "./pages/Orders";
8 | import Home from "./pages/Home";
9 | import ScrollToTop from "./components/ScrollToTop";
10 | import Users from "./pages/Users";
11 | import LoginPage from "./pages/LoginPage";
12 | import RegisterPage from "./pages/RegisterPage";
13 | import ProtectedRoute from "./components/ProtectedRoute";
14 | import Analytics from "./pages/Analytics";
15 | import Inventory from "./pages/Inventory";
16 | import Invoice from "./pages/Invoice";
17 | import Categories from "./pages/Categories";
18 | import Brands from "./pages/Brands";
19 | import ApiDocumentation from "./pages/ApiDocumentation";
20 | import Contacts from "./pages/Contacts";
21 |
22 | function App() {
23 | const { token } = useSelector((state) => state.auth);
24 |
25 | return (
26 |
27 |
28 | {/* Public Routes */}
29 | } />
30 | } />
31 |
32 | {/* Protected Routes */}
33 |
37 |
38 | {/* Premium Support Badge */}
39 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | } />
64 | } />
65 | } />
66 | } />
67 | } />
68 | } />
69 | } />
70 | }
73 | />
74 | } />
75 | } />
76 | } />
77 | } />
78 |
79 |
80 |
81 |
82 |
83 | }
84 | />
85 |
86 |
87 | );
88 | }
89 |
90 | export default App;
91 |
--------------------------------------------------------------------------------
/admin/src/components/PremiumModal.jsx:
--------------------------------------------------------------------------------
1 | import { FaCrown, FaStar, FaTimes } from "react-icons/fa";
2 | import { HiSparkles } from "react-icons/hi";
3 | import PropTypes from "prop-types";
4 |
5 | const PremiumModal = ({
6 | isOpen,
7 | onClose,
8 | title = "Premium Feature",
9 | description = "This feature is available in the premium version only.",
10 | features = [],
11 | }) => {
12 | if (!isOpen) return null;
13 |
14 | const defaultFeatures = [
15 | "Advanced order management",
16 | "Detailed analytics & reports",
17 | "Priority customer support",
18 | "Custom branding options",
19 | "API access and integrations",
20 | ];
21 |
22 | const featureList = features.length > 0 ? features : defaultFeatures;
23 |
24 | return (
25 |
26 |
27 | {/* Header */}
28 |
29 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
{title}
44 |
45 |
46 |
47 |
{description}
48 |
49 |
50 |
51 | {/* Content */}
52 |
53 | {/* Features */}
54 |
55 |
56 |
57 |
What you get:
58 |
59 |
60 |
61 | {featureList.map((feature, index) => (
62 |
63 |
64 | ✓
65 |
66 |
67 | {feature}
68 |
69 |
70 | ))}
71 |
72 |
73 |
74 | {/* CTA */}
75 |
91 |
92 |
93 |
94 | );
95 | };
96 |
97 | PremiumModal.propTypes = {
98 | isOpen: PropTypes.bool.isRequired,
99 | onClose: PropTypes.func.isRequired,
100 | title: PropTypes.string,
101 | description: PropTypes.string,
102 | features: PropTypes.array,
103 | };
104 |
105 | export default PremiumModal;
106 |
--------------------------------------------------------------------------------
/client/src/components/homeProducts/NewArrivals.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import Slider from "react-slick";
3 | import NextArrow from "../NextArrow";
4 | import PreviousArrow from "../PreviousArrow";
5 | import Title from "../ui/title";
6 | import ProductCard from "../ProductCard";
7 | import { getData } from "../../helpers";
8 | import { config } from "../../../config";
9 |
10 | const NewArrivals = () => {
11 | const settings = {
12 | infinite: true,
13 | speed: 500,
14 | slidesToShow: 4,
15 | slidesToScroll: 1,
16 | nextArrow: ,
17 | prevArrow: ,
18 | responsive: [
19 | {
20 | breakpoint: 1025,
21 | settings: {
22 | slidesToShow: 3,
23 | slidesToScroll: 1,
24 | infinite: true,
25 | },
26 | },
27 | {
28 | breakpoint: 769,
29 | settings: {
30 | slidesToShow: 2,
31 | slidesToScroll: 2,
32 | infinite: true,
33 | },
34 | },
35 | {
36 | breakpoint: 480,
37 | settings: {
38 | slidesToShow: 1,
39 | slidesToScroll: 1,
40 | infinite: true,
41 | },
42 | },
43 | ],
44 | };
45 |
46 | const [products, setProducts] = useState([]);
47 | const [loading, setLoading] = useState(true);
48 | const endpoint = `${config?.baseUrl}/api/products?_type=new_arrivals`;
49 |
50 | useEffect(() => {
51 | const getProducts = async () => {
52 | setLoading(true);
53 | try {
54 | const data = await getData(endpoint);
55 | // Handle the new API response format that includes success field
56 | setProducts(data?.products || []);
57 | } catch (error) {
58 | console.error("Error fetching products:", error);
59 | setProducts([]);
60 | } finally {
61 | setLoading(false);
62 | }
63 | };
64 | getProducts();
65 | }, []);
66 |
67 | // Render skeleton loading state
68 | if (loading) {
69 | return (
70 |
71 |
72 |
New Arrivals
73 |
74 |
75 | {Array.from({ length: 4 }).map((_, index) => (
76 |
87 | ))}
88 |
89 |
90 | );
91 | }
92 |
93 | return (
94 |
95 |
96 |
New Arrivals
97 | {/* See all */}
98 |
99 |
100 | {/* Conditionally render slider or grid based on product count */}
101 | {products && products.length > 3 ? (
102 | // Use slider when more than 3 products
103 |
104 | {products?.map((item) => (
105 |
108 | ))}
109 |
110 | ) : (
111 | // Use simple grid when 3 or fewer products
112 |
113 | {products?.map((item) => (
114 |
115 | ))}
116 |
117 | )}
118 |
119 | {/* Show message when no products */}
120 | {(!products || products.length === 0) && (
121 |
122 |
No new arrivals available at the moment.
123 |
124 | )}
125 |
126 | );
127 | };
128 |
129 | export default NewArrivals;
130 |
--------------------------------------------------------------------------------
/client/src/components/homeProducts/BestSellers.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import Slider from "react-slick";
3 | import NextArrow from "../NextArrow";
4 | import PreviousArrow from "../PreviousArrow";
5 | import Title from "../ui/title";
6 | import ProductCard from "../ProductCard";
7 | import { getData } from "../../helpers";
8 | import { config } from "../../../config";
9 |
10 | const BestSellers = () => {
11 | const settings = {
12 | infinite: true,
13 | speed: 500,
14 | slidesToShow: 4,
15 | slidesToScroll: 1,
16 | nextArrow: ,
17 | prevArrow: ,
18 | responsive: [
19 | {
20 | breakpoint: 1025,
21 | settings: {
22 | slidesToShow: 3,
23 | slidesToScroll: 1,
24 | infinite: true,
25 | },
26 | },
27 | {
28 | breakpoint: 769,
29 | settings: {
30 | slidesToShow: 2,
31 | slidesToScroll: 2,
32 | infinite: true,
33 | },
34 | },
35 | {
36 | breakpoint: 480,
37 | settings: {
38 | slidesToShow: 1,
39 | slidesToScroll: 1,
40 | infinite: true,
41 | },
42 | },
43 | ],
44 | };
45 |
46 | const [products, setProducts] = useState([]);
47 | const [loading, setLoading] = useState(true);
48 | const endpoint = `${config?.baseUrl}/api/products?_type=best_sellers`;
49 |
50 | useEffect(() => {
51 | const getProducts = async () => {
52 | setLoading(true);
53 | try {
54 | const data = await getData(endpoint);
55 | // Handle the new API response format that includes success field
56 | setProducts(data?.products || []);
57 | } catch (error) {
58 | console.error("Error fetching products:", error);
59 | setProducts([]);
60 | } finally {
61 | setLoading(false);
62 | }
63 | };
64 | getProducts();
65 | }, []);
66 |
67 | // Render skeleton loading state
68 | if (loading) {
69 | return (
70 |
71 |
72 |
Our Bestsellers
73 |
74 |
75 | {Array.from({ length: 4 }).map((_, index) => (
76 |
87 | ))}
88 |
89 |
90 | );
91 | }
92 |
93 | return (
94 |
95 |
96 |
Our Bestsellers
97 | {/* See all */}
98 |
99 |
100 | {/* Conditionally render slider or grid based on product count */}
101 | {products && products.length > 3 ? (
102 | // Use slider when more than 3 products
103 |
104 | {products?.map((item) => (
105 |
108 | ))}
109 |
110 | ) : (
111 | // Use simple grid when 3 or fewer products
112 |
113 | {products?.map((item) => (
114 |
115 | ))}
116 |
117 | )}
118 |
119 | {/* Show message when no products */}
120 | {(!products || products.length === 0) && (
121 |
122 |
No bestsellers available at the moment.
123 |
124 | )}
125 |
126 | );
127 | };
128 |
129 | export default BestSellers;
130 |
--------------------------------------------------------------------------------
/client/src/components/homeProducts/SpecialOffers.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import Slider from "react-slick";
3 | import NextArrow from "../NextArrow";
4 | import PreviousArrow from "../PreviousArrow";
5 | import Title from "../ui/title";
6 | import ProductCard from "../ProductCard";
7 | import { getData } from "../../helpers";
8 | import { config } from "../../../config";
9 |
10 | const SpecialOffers = () => {
11 | const settings = {
12 | infinite: true,
13 | speed: 500,
14 | slidesToShow: 4,
15 | slidesToScroll: 1,
16 | nextArrow: ,
17 | prevArrow: ,
18 | responsive: [
19 | {
20 | breakpoint: 1025,
21 | settings: {
22 | slidesToShow: 3,
23 | slidesToScroll: 1,
24 | infinite: true,
25 | },
26 | },
27 | {
28 | breakpoint: 769,
29 | settings: {
30 | slidesToShow: 2,
31 | slidesToScroll: 2,
32 | infinite: true,
33 | },
34 | },
35 | {
36 | breakpoint: 480,
37 | settings: {
38 | slidesToShow: 1,
39 | slidesToScroll: 1,
40 | infinite: true,
41 | },
42 | },
43 | ],
44 | };
45 | const [products, setProducts] = useState([]);
46 | const [loading, setLoading] = useState(true);
47 | // const endpoint = "http://localhost:8000/products/?_type=offers";
48 | const endpoint = `${config?.baseUrl}/api/products?offer=true`;
49 |
50 | useEffect(() => {
51 | const getProducts = async () => {
52 | setLoading(true);
53 | try {
54 | const data = await getData(endpoint);
55 | // Handle the new API response format that includes success field
56 | setProducts(data?.products || []);
57 | } catch (error) {
58 | console.error("Error fetching products:", error);
59 | setProducts([]);
60 | } finally {
61 | setLoading(false);
62 | }
63 | };
64 | getProducts();
65 | }, []);
66 |
67 | // Render skeleton loading state
68 | if (loading) {
69 | return (
70 |
71 |
72 |
Special Offers
73 |
74 |
75 | {Array.from({ length: 4 }).map((_, index) => (
76 |
87 | ))}
88 |
89 |
90 | );
91 | }
92 |
93 | return (
94 |
95 |
96 |
Special Offers
97 |
98 |
99 | {/* Conditionally render slider or grid based on product count */}
100 | {products && products.length > 3 ? (
101 | // Use slider when more than 3 products
102 |
103 | {products?.map((item) => (
104 |
107 | ))}
108 |
109 | ) : (
110 | // Use simple grid when 3 or fewer products
111 |
112 | {products?.map((item) => (
113 |
114 | ))}
115 |
116 | )}
117 |
118 | {/* Show message when no products */}
119 | {(!products || products.length === 0) && (
120 |
121 |
No special offers available at the moment.
122 |
123 | )}
124 |
125 | );
126 | };
127 |
128 | export default SpecialOffers;
129 |
--------------------------------------------------------------------------------
/client/src/pages/Wishlist.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import { Link } from "react-router-dom";
3 | import Container from "../components/Container";
4 | import { FaHeart, FaShoppingBag, FaArrowLeft } from "react-icons/fa";
5 |
6 | const Wishlist = () => {
7 | return (
8 |
9 |
10 |
11 | {/* Header */}
12 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | My Wishlist
25 |
26 |
27 | Save your favorite items for later
28 |
29 |
30 |
31 |
35 |
36 | Back to Profile
37 |
38 |
39 |
40 |
41 | {/* Empty State */}
42 |
48 |
49 |
50 |
51 |
52 | Your wishlist is empty
53 |
54 |
55 | Start building your wishlist by adding items you love. You can
56 | save items while browsing and come back to them later.
57 |
58 |
62 |
63 | Start Shopping
64 |
65 |
66 |
67 | {/* Feature Info */}
68 |
74 |
75 |
76 |
77 |
78 |
79 | Save Favorites
80 |
81 |
82 | Keep track of items you love and want to purchase later
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | Quick Purchase
91 |
92 |
93 | Easily move items from wishlist to cart when ready to buy
94 |
95 |
96 |
97 |
98 |
99 |
100 |
Never Forget
101 |
102 | Your wishlist syncs across devices so you never lose your
103 | favorites
104 |
105 |
106 |
107 |
108 |
109 |
110 | );
111 | };
112 |
113 | export default Wishlist;
114 |
--------------------------------------------------------------------------------
/client/src/components/InteractiveMap.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { FaMapMarkerAlt, FaDirections, FaPhone, FaClock } from "react-icons/fa";
3 |
4 | // Fallback Interactive Map Component (avoiding react-leaflet render2 error)
5 | const InteractiveMap = () => {
6 | const [showDirections, setShowDirections] = useState(false);
7 |
8 | // Coordinates for Broadway Street, New York
9 | const position = [40.8176, -73.9482];
10 | const address = "3065 Broadway Street, New York, NY 10027, United States";
11 |
12 | const openInGoogleMaps = () => {
13 | const url = `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent(
14 | address
15 | )}`;
16 | window.open(url, "_blank", "noopener,noreferrer");
17 | };
18 |
19 | const openInAppleMaps = () => {
20 | const url = `http://maps.apple.com/?daddr=${encodeURIComponent(address)}`;
21 | window.open(url, "_blank", "noopener,noreferrer");
22 | };
23 |
24 | return (
25 |
26 | {/* Static Map Image with Store Location */}
27 |
28 | {/* Map Placeholder with Store Info */}
29 |
35 |
36 | {/* Store Marker Overlay */}
37 |
38 |
39 |
40 |
41 |
42 | Orebi Shopping
43 |
44 |
45 |
46 |
47 |
48 | {/* Controls Overlay */}
49 |
50 | setShowDirections(!showDirections)}
52 | className="bg-white rounded-lg shadow-md p-2 hover:shadow-lg transition-shadow"
53 | title="Get Directions"
54 | >
55 |
56 |
57 |
58 |
59 |
60 | {/* Store Information Panel */}
61 |
62 |
63 |
64 |
65 | Orebi Shopping
66 |
67 |
68 |
69 |
70 | {address}
71 |
72 |
73 |
74 | (415) 225-0123
75 |
76 |
77 |
78 |
79 |
Mon-Fri: 9:00 AM - 8:00 PM
80 |
Sat-Sun: 10:00 AM - 6:00 PM
81 |
82 |
83 |
84 |
85 |
86 |
87 | {/* Direction Buttons */}
88 | {showDirections && (
89 |
90 |
91 | Get Directions:
92 |
93 |
94 |
98 |
99 | Google Maps
100 |
101 |
105 |
106 | Apple Maps
107 |
108 |
109 |
110 | )}
111 |
112 |
113 | );
114 | };
115 |
116 | export default InteractiveMap;
117 |
--------------------------------------------------------------------------------
/admin/src/pages/Inventory.jsx:
--------------------------------------------------------------------------------
1 | import { FaBoxes, FaExclamationTriangle, FaCheckCircle } from "react-icons/fa";
2 | import { MdOutlineInventory, MdLowPriority } from "react-icons/md";
3 |
4 | const Inventory = () => {
5 | const inventoryStats = [
6 | {
7 | title: "Total Products",
8 | value: "156",
9 | icon: ,
10 | color: "blue",
11 | },
12 | {
13 | title: "Low Stock Items",
14 | value: "12",
15 | icon: ,
16 | color: "yellow",
17 | },
18 | {
19 | title: "Out of Stock",
20 | value: "3",
21 | icon: ,
22 | color: "red",
23 | },
24 | {
25 | title: "In Stock",
26 | value: "141",
27 | icon: ,
28 | color: "green",
29 | },
30 | ];
31 |
32 | const lowStockItems = [
33 | { name: "iPhone 14 Pro", stock: 5, threshold: 10 },
34 | { name: "MacBook Pro", stock: 2, threshold: 5 },
35 | { name: "iPad Air", stock: 8, threshold: 15 },
36 | { name: "Apple Watch", stock: 3, threshold: 10 },
37 | ];
38 |
39 | return (
40 |
41 |
42 |
43 | Inventory Management
44 |
45 |
46 | Monitor and manage your product inventory
47 |
48 |
49 |
50 | {/* Inventory Stats */}
51 |
52 | {inventoryStats.map((stat, index) => (
53 |
57 |
58 |
61 | {stat.icon}
62 |
63 |
64 |
65 | {stat.value}
66 |
67 |
{stat.title}
68 |
69 |
70 |
71 | ))}
72 |
73 |
74 | {/* Low Stock Alert */}
75 |
76 |
77 |
78 |
79 |
80 | Low Stock Alert
81 |
82 |
83 |
84 |
85 |
86 | {lowStockItems.map((item, index) => (
87 |
91 |
92 |
{item.name}
93 |
94 | Threshold: {item.threshold} units
95 |
96 |
97 |
98 |
99 | {item.stock}
100 |
101 |
units left
102 |
103 |
104 | ))}
105 |
106 |
107 |
108 |
109 | {/* Quick Actions */}
110 |
111 |
112 |
Quick Actions
113 |
114 |
115 |
116 |
117 |
118 |
119 | Update Inventory
120 |
121 |
122 |
123 |
124 | Bulk Import
125 |
126 |
127 |
128 | Stock Audit
129 |
130 |
131 |
132 |
133 |
134 | );
135 | };
136 |
137 | export default Inventory;
138 |
--------------------------------------------------------------------------------
/admin/src/pages/Invoice.jsx:
--------------------------------------------------------------------------------
1 | import { FaFileInvoice, FaLock, FaHeart } from "react-icons/fa";
2 |
3 | const Invoice = () => {
4 | return (
5 |
6 | {/* Header */}
7 |
8 |
9 |
10 | Invoice Management
11 |
12 |
13 | Generate professional invoices from customer orders
14 |
15 |
16 |
17 |
18 | {/* Premium Message */}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 🔒 Premium Invoice Features
27 |
28 |
29 | Professional invoice generation, PDF exports, order management,
30 | and advanced billing features are available in the premium
31 | version.
32 |
33 |
34 |
35 | {/* Premium Features List */}
36 |
37 |
38 | 💎 Premium Invoice Features
39 |
40 |
41 |
42 | ✓
43 | Professional invoice generation
44 |
45 |
46 | ✓
47 | PDF export & download
48 |
49 |
50 | ✓
51 | Order filtering & search
52 |
53 |
54 | ✓
55 | Bulk invoice creation
56 |
57 |
58 | ✓
59 | Print & share functionality
60 |
61 |
62 | ✓
63 | Detailed billing breakdown
64 |
65 |
66 |
67 | ⚡ Complete admin panel • Professional invoices • Full source code
68 |
69 |
70 |
71 | {/* CTA Button */}
72 |
78 |
79 | Get Premium Access Now
80 |
81 |
82 |
83 | Unlock professional invoice management and get the complete admin
84 | panel
85 |
86 |
87 | {/* Demo Preview */}
88 |
89 |
90 |
91 | Invoice Management Preview
92 |
93 |
94 |
95 | Order #12345678
96 | $299.99
97 |
98 |
99 | Order #87654321
100 | $459.50
101 |
102 |
103 | Order #45678912
104 | $129.75
105 |
106 |
107 | + Professional invoice generation, PDF exports, and more...
108 |
109 |
110 |
111 |
112 |
113 |
114 | );
115 | };
116 |
117 | export default Invoice;
118 |
--------------------------------------------------------------------------------
/client/src/pages/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import { Link } from "react-router-dom";
3 | import Container from "../components/Container";
4 | import { FaHome, FaSearch, FaArrowLeft } from "react-icons/fa";
5 | import { MdError } from "react-icons/md";
6 |
7 | const NotFound = () => {
8 | const popularLinks = [
9 | { name: "Shop All Products", path: "/shop" },
10 | { name: "About Us", path: "/about" },
11 | { name: "Contact Support", path: "/contact" },
12 | { name: "My Account", path: "/signin" },
13 | ];
14 |
15 | return (
16 |
17 |
18 |
19 | {/* Error Illustration */}
20 |
26 |
27 |
404
28 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | {/* Error Message */}
42 |
48 |
49 | Oops! Page Not Found
50 |
51 |
52 | The page you're looking for seems to have wandered off.
53 |
54 |
55 | Don't worry, even the best explorers get lost sometimes!
56 |
57 |
58 |
59 | {/* Action Buttons */}
60 |
66 |
67 |
68 |
69 | Go Home
70 |
71 |
72 |
73 |
74 |
75 | Browse Products
76 |
77 |
78 | window.history.back()}
80 | className="flex items-center gap-2 text-gray-600 px-8 py-4 rounded-lg hover:text-gray-800 transition-colors font-semibold"
81 | >
82 |
83 | Go Back
84 |
85 |
86 |
87 | {/* Popular Links */}
88 |
94 |
95 | Popular Pages
96 |
97 |
98 | {popularLinks.map((link, index) => (
99 |
104 |
105 | {link.name}
106 |
107 |
108 | ))}
109 |
110 |
111 |
112 | {/* Fun Fact */}
113 |
119 | 🛍️
120 |
121 | Did you know? While you're here, over 1000
122 | customers are shopping on our site right now!
123 |
124 |
125 |
126 |
127 |
128 | );
129 | };
130 |
131 | export default NotFound;
132 |
--------------------------------------------------------------------------------