├── src
├── components
│ ├── SearchBar.jsx
│ ├── Loader.jsx
│ ├── allComponents.js
│ ├── FeaturedCategories.js
│ ├── Footer.js
│ ├── OrderSuccessCard.jsx
│ ├── MobileMenu.jsx
│ ├── AddressCard.jsx
│ ├── HorizontalCard.js
│ ├── ProductCard.js
│ ├── Navbar.js
│ ├── CartPriceCard.js
│ ├── FiltersBar.js
│ └── EditAddressModal.jsx
├── logo.png
├── App.css
├── styles
│ ├── pages
│ │ ├── errorPage.css
│ │ ├── wishlist.css
│ │ ├── forgotpassword.css
│ │ ├── searchPage.css
│ │ ├── profile.css
│ │ ├── signup.css
│ │ ├── login.css
│ │ ├── singleProductPage.css
│ │ ├── cart.css
│ │ ├── productlist.css
│ │ └── home.css
│ ├── layouts
│ │ ├── footer.css
│ │ ├── scrollbar.css
│ │ └── navbar.css
│ ├── components
│ │ ├── addressCard.css
│ │ ├── orderCard.css
│ │ ├── loader.css
│ │ ├── editAddressModal.css
│ │ └── mobileMenu.css
│ └── utilities
│ │ └── variables.css
├── utils
│ ├── isItemInWishList.js
│ ├── isItemInCart.js
│ ├── filterReducer.js
│ └── filterProducts.js
├── routes
│ ├── RequiresAuth.js
│ └── PageRoutes.js
├── App.js
├── reducers
│ ├── wishListReducer.js
│ ├── cartReducer.js
│ └── addressReducer.js
├── backend
│ ├── db
│ │ ├── users.js
│ │ ├── categories.js
│ │ └── products.js
│ ├── utils
│ │ └── authUtils.js
│ └── controllers
│ │ ├── ProductController.js
│ │ ├── CategoryController.js
│ │ ├── AuthController.js
│ │ ├── WishlistController.js
│ │ └── CartController.js
├── context
│ ├── cart-context.js
│ ├── product-context.js
│ ├── address-context.js
│ ├── auth-context.js
│ └── wishlist-context.js
├── services
│ ├── getProducts.js
│ ├── getFeaturedCategories.js
│ ├── getProductById.js
│ ├── wishlistServices.js
│ └── cartServices.js
├── pages
│ ├── ErrorPage.jsx
│ ├── OrderSuccessPage.jsx
│ ├── WishList.js
│ ├── Checkout.jsx
│ ├── Cart.js
│ ├── ForgotPassword.js
│ ├── Home.js
│ ├── ProductListing.js
│ ├── SearchPage.jsx
│ ├── MyProfile.jsx
│ ├── Login.js
│ ├── SingleProductPage.jsx
│ └── Signup.js
├── index.js
└── server.js
├── public
├── _redirects
├── shop.ico
├── favicon.ico
└── index.html
├── .gitattributes
├── .gitignore
├── .eslintrc.json
├── package.json
└── README.md
/src/components/SearchBar.jsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/src/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kedark152/AgroStores-react/HEAD/src/logo.png
--------------------------------------------------------------------------------
/public/shop.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kedark152/AgroStores-react/HEAD/public/shop.ico
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kedark152/AgroStores-react/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | @import url(./styles/layouts/scrollbar.css);
2 | .App {
3 | height: 100vh;
4 | }
5 |
--------------------------------------------------------------------------------
/src/styles/pages/errorPage.css:
--------------------------------------------------------------------------------
1 | .error-img {
2 | height: 80vh;
3 | }
4 |
5 | .error-page-container {
6 | font-family: var(--lato-font);
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/Loader.jsx:
--------------------------------------------------------------------------------
1 | import "../styles/components/loader.css";
2 |
3 | export const Loader = () => {
4 | return (
5 |
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/src/styles/layouts/footer.css:
--------------------------------------------------------------------------------
1 | footer{
2 | background-color: var(--primary);
3 | color: var(--white);
4 | font-family: var(--acme-font);
5 | }
6 |
7 | .footer-links a{
8 | color: var(--white);
9 | }
10 |
11 | .fab:hover{
12 | color: var(--grey-dark);
13 | cursor: pointer;
14 | }
15 |
--------------------------------------------------------------------------------
/src/utils/isItemInWishList.js:
--------------------------------------------------------------------------------
1 | import {useWishList} from "../context/wishlist-context";
2 |
3 | export const IsItemInWishList = (id)=>{
4 | const {wishListState} = useWishList();
5 | let itemStatus = false;
6 | wishListState.wishlistItems.map((item)=>{
7 | item._id === id ? (itemStatus = true) : null
8 | }
9 | )
10 | return itemStatus;
11 | }
--------------------------------------------------------------------------------
/src/utils/isItemInCart.js:
--------------------------------------------------------------------------------
1 | import { useCart } from "../context/cart-context";
2 |
3 | export const IsItemInCart = (id) => {
4 | const { cartState } = useCart();
5 | let itemStatus = false;
6 | if (cartState.cartItems.length > 0) {
7 | cartState.cartItems.map((item) => {
8 | item._id === id ? (itemStatus = true) : null;
9 | });
10 | }
11 | return itemStatus;
12 | };
13 |
--------------------------------------------------------------------------------
/src/routes/RequiresAuth.js:
--------------------------------------------------------------------------------
1 | import { Navigate, Outlet, useLocation } from "react-router-dom";
2 | import { useAuth } from "../context/auth-context";
3 |
4 | export const RequiresAuth = () => {
5 | const { auth } = useAuth();
6 | const location = useLocation();
7 | return auth.token ? (
8 |
9 | ) : (
10 |
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 | .eslintcache
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import "./styles/utilities/variables.css";
3 | import { PageRoutes } from "./routes/PageRoutes";
4 | import "react-toastify/dist/ReactToastify.css";
5 |
6 | import { ToastContainer } from "react-toastify";
7 |
8 | function App() {
9 | return (
10 |
14 | );
15 | }
16 |
17 | export default App;
18 |
--------------------------------------------------------------------------------
/src/components/allComponents.js:
--------------------------------------------------------------------------------
1 | import {FeaturedCategories} from "./FeaturedCategories"
2 | import { FiltersBar } from "./FiltersBar"
3 | import { Navbar } from "./Navbar";
4 | import { Footer } from "./Footer"
5 | import { ProductCard } from "./ProductCard";
6 | import { HorizontalCard } from "./HorizontalCard";
7 | import { CartPriceCard } from "./CartPriceCard";
8 |
9 |
10 | export {FeaturedCategories,FiltersBar,Navbar,Footer,ProductCard, HorizontalCard, CartPriceCard};
11 |
--------------------------------------------------------------------------------
/src/reducers/wishListReducer.js:
--------------------------------------------------------------------------------
1 | export const wishListReducer = (state, { type, payload }) => {
2 | switch (type) {
3 | case "ADD-TO-WISHLIST":
4 | case "UPDATE-WISHLIST":
5 | return { ...state, wishlistItems: payload };
6 | case "REMOVE-FROM-WISHLIST":
7 | return { ...state, wishlistItems: payload };
8 | case "CLEAR-WISHLIST":
9 | return wishListInitialState;
10 | default:
11 | state;
12 | }
13 | };
14 |
15 | export const wishListInitialState = {
16 | wishlistItems: [],
17 | };
18 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true,
5 | "node": true
6 | },
7 | "extends": [
8 | "eslint:recommended",
9 | "plugin:react/recommended",
10 | "plugin:react/jsx-runtime",
11 | "plugin:react-hooks/recommended"
12 | ],
13 | "parserOptions": {
14 | "ecmaFeatures": {
15 | "jsx": true
16 | },
17 | "ecmaVersion": 12,
18 | "sourceType": "module"
19 | },
20 | "plugins": ["react"],
21 | "rules": {
22 | "react/prop-types": "off"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/styles/components/addressCard.css:
--------------------------------------------------------------------------------
1 | .address-box {
2 | padding: 2rem;
3 | background-color: var(--white);
4 | gap: 10px;
5 | box-shadow: var(--box-shadow-simple);
6 | }
7 |
8 | .single-address {
9 | gap: 8px;
10 | }
11 |
12 | .address-select-input {
13 | accent-color: var(--primary);
14 | }
15 | .btn-edit-address {
16 | margin-left: var(--size-md);
17 | }
18 | @media screen and (max-width: 800px) {
19 | .address-box {
20 | padding: 1rem;
21 | }
22 | .btn-edit-address {
23 | margin-left: var(--size-xsm);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/styles/layouts/scrollbar.css:
--------------------------------------------------------------------------------
1 | /* Scroll-bar styling */
2 | /* Works on Firefox */
3 | * {
4 | scrollbar-width: thin;
5 | scrollbar-color: var(--primary-transparent) var(--primary);
6 | }
7 |
8 | /* Works on Chrome, Edge, and Safari */
9 | *::-webkit-scrollbar {
10 | width: 10px;
11 | }
12 |
13 | *::-webkit-scrollbar-track {
14 | background: var(--primary-transparent);
15 | }
16 |
17 | *::-webkit-scrollbar-thumb {
18 | background-color: var(--primary-hover);
19 | border-radius: 10px;
20 | border: 1px solid var(--primary-hover);
21 | }
22 |
--------------------------------------------------------------------------------
/src/backend/db/users.js:
--------------------------------------------------------------------------------
1 | import { v4 as uuid } from "uuid";
2 | import { formatDate } from "../utils/authUtils";
3 | /**
4 | * User Database can be added here.
5 | * You can add default users of your wish with different attributes
6 | * Every user will have cart (Quantity of all Products in Cart is set to 1 by default), wishList by default
7 | * */
8 |
9 | export const users = [
10 | {
11 | _id: uuid(),
12 | firstName: "Kedar",
13 | lastName: "Kulkarni",
14 | email: "kedar@gmail.com",
15 | password: "kedarInGoa@123",
16 | createdAt: formatDate(),
17 | updatedAt: formatDate(),
18 | },
19 | ];
20 |
--------------------------------------------------------------------------------
/src/context/cart-context.js:
--------------------------------------------------------------------------------
1 | import {createContext,useContext,useReducer} from "react";
2 | import { cartReducer, cartInitialState } from "../reducers/cartReducer";
3 |
4 | const CartContext = createContext(null);
5 |
6 | // eslint-disable-next-line react/prop-types
7 | export const CartProvider = ({children}) =>{
8 | const [cartState, dispatchCart] = useReducer(cartReducer, cartInitialState);
9 |
10 | return(
11 |
12 | {children}
13 |
14 | )
15 |
16 | }
17 |
18 | export const useCart = () => useContext(CartContext);
--------------------------------------------------------------------------------
/src/context/product-context.js:
--------------------------------------------------------------------------------
1 | import {createContext,useContext,useReducer} from "react";
2 | import { filterReducer, filterInitialState } from "../utils/filterReducer";
3 |
4 | const ProductContext = createContext(null);
5 |
6 | // eslint-disable-next-line react/prop-types
7 | export const ProductProvider = ({children}) =>{
8 | const [filterState, dispatchFilters] = useReducer(filterReducer, filterInitialState);
9 |
10 | return(
11 |
12 | {children}
13 |
14 | )
15 |
16 | }
17 |
18 | export const useProduct = ()=> useContext(ProductContext);
19 |
--------------------------------------------------------------------------------
/src/services/getProducts.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import {useState,useEffect} from "react";
3 |
4 |
5 | export const GetProducts = ()=>{
6 | const [loader, setLoader] = useState(true);
7 | const [products, setProducts] = useState([{}]);
8 |
9 |
10 | useEffect(() => {
11 | (async function prodLoader() {
12 | try {
13 | const response = await axios.get("/api/products");
14 | const getProducts = response.data.products;
15 | setLoader(false);
16 | setProducts(getProducts);
17 | } catch (error) {
18 | console.error(error);
19 | }
20 | })(); //IIFE - Immediately Invoked Function
21 | }, []);
22 | return {loader,products};
23 | }
24 |
--------------------------------------------------------------------------------
/src/styles/pages/wishlist.css:
--------------------------------------------------------------------------------
1 | .wishlist-body {
2 | font-family: var(--lato-font);
3 | background-color: var(--lightest-green);
4 | padding-bottom: 1rem;
5 | margin: 0;
6 | min-height: 100vh;
7 | max-height: max-content;
8 | }
9 |
10 | .wishlist-container-main {
11 | justify-content: center;
12 | width: 100%;
13 | gap: 1rem;
14 | flex-wrap: wrap;
15 | }
16 | .heart-icon-wishlist {
17 | position: absolute;
18 | z-index: 2;
19 | top: 0.1rem;
20 | right: 0.1rem;
21 | font-size: 1.6rem;
22 | cursor: pointer;
23 | background-color: var(--lightest-green);
24 | padding: 5px;
25 | border-radius: 50%;
26 | color: var(--danger);
27 | font-weight: bold;
28 | }
29 |
--------------------------------------------------------------------------------
/src/context/address-context.js:
--------------------------------------------------------------------------------
1 | import { useContext, useReducer } from "react";
2 | import { createContext } from "react";
3 | import {
4 | addressInitialState,
5 | addressReducer,
6 | } from "../reducers/addressReducer";
7 |
8 | const AddressContext = createContext();
9 |
10 | export const AddressProvider = ({ children }) => {
11 | const [addressState, dispatchAddress] = useReducer(
12 | addressReducer,
13 | addressInitialState
14 | );
15 |
16 | return (
17 |
18 | {children}
19 |
20 | );
21 | };
22 |
23 | export const useAddress = () => useContext(AddressContext);
24 |
--------------------------------------------------------------------------------
/src/context/auth-context.js:
--------------------------------------------------------------------------------
1 | import { useState, useContext, createContext, useEffect } from "react";
2 |
3 | const AuthContext = createContext();
4 |
5 | const AuthProvider = ({ children }) => {
6 | const [auth, setAuth] = useState({});
7 | useEffect(() => {
8 | const token = localStorage.getItem("token");
9 | token
10 | ? setAuth({ token: token, isLoggedIn: true })
11 | : setAuth({ token: "", isLoggedIn: false });
12 | }, []);
13 |
14 | return (
15 |
16 | {children}
17 |
18 | );
19 | };
20 |
21 | const useAuth = () => useContext(AuthContext);
22 |
23 | export { AuthProvider, useAuth };
24 |
--------------------------------------------------------------------------------
/src/services/getFeaturedCategories.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { useState, useEffect } from "react";
3 |
4 | export const GetFeaturedCategories = ()=>{
5 | const [categories,setCategories] = useState([]);
6 | useEffect(()=>{
7 | (async function categoriesLoader(){
8 | try{
9 | const response = await axios.get("/api/categories");
10 | const getCategories = (response.data.categories);
11 | setCategories(getCategories);
12 | }
13 | catch (error){
14 | console.log("getFeaturedCategories.js Error:",error);
15 | }
16 | })();
17 | },[]);
18 | return categories;
19 | }
20 |
--------------------------------------------------------------------------------
/src/styles/components/orderCard.css:
--------------------------------------------------------------------------------
1 | .order-box {
2 | padding: 2rem;
3 | background-color: var(--white);
4 | gap: 10px;
5 | box-shadow: var(--box-shadow-simple);
6 | }
7 | .btn-continue-shopping {
8 | width: 14rem;
9 | margin-left: 10rem;
10 | padding: 1rem;
11 | }
12 | .success-tick-img {
13 | width: 8rem;
14 | margin-left: 14rem;
15 | }
16 |
17 | @media screen and (max-width: 800px) {
18 | .order-box {
19 | padding: 1rem;
20 | }
21 | .success-tick-img {
22 | width: 5rem;
23 | margin-left: 8rem;
24 | }
25 | .payment-text,
26 | .email-msg-text {
27 | font-size: 1rem;
28 | }
29 | .btn-continue-shopping {
30 | width: 12rem;
31 | margin-left: 5rem;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/context/wishlist-context.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useReducer } from "react";
2 | import {
3 | wishListReducer,
4 | wishListInitialState,
5 | } from "../reducers/wishListReducer";
6 |
7 | const WishListContext = createContext(null);
8 |
9 | // eslint-disable-next-line react/prop-types
10 | export const WishListProvider = ({ children }) => {
11 | const [wishListState, dispatchWishList] = useReducer(
12 | wishListReducer,
13 | wishListInitialState
14 | );
15 |
16 | return (
17 |
18 | {children}
19 |
20 | );
21 | };
22 |
23 | export const useWishList = () => useContext(WishListContext);
24 |
--------------------------------------------------------------------------------
/src/backend/utils/authUtils.js:
--------------------------------------------------------------------------------
1 | import { Response } from "miragejs";
2 | import dayjs from "dayjs";
3 | import jwt_decode from "jwt-decode";
4 |
5 | export const requiresAuth = function (request) {
6 | const encodedToken = request.requestHeaders.authorization;
7 | const decodedToken = jwt_decode(
8 | encodedToken,
9 | process.env.REACT_APP_JWT_SECRET
10 | );
11 | if (decodedToken) {
12 | const user = this.db.users.findBy({ email: decodedToken.email });
13 | if (user) {
14 | return user._id;
15 | }
16 | }
17 | return new Response(
18 | 401,
19 | {},
20 | { errors: ["The token is invalid. Unauthorized access error."] }
21 | );
22 | };
23 |
24 | export const formatDate = () => dayjs().format("YYYY-MM-DDTHH:mm:ssZ");
25 |
--------------------------------------------------------------------------------
/src/services/getProductById.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { useState, useEffect } from "react";
3 |
4 | export const GetProductById = (productId) => {
5 | const [loader, setLoader] = useState(true);
6 | const [product, setProduct] = useState([{}]);
7 |
8 | useEffect(() => {
9 | (async function productById() {
10 | try {
11 | const response = await axios.get(`/api/products/${productId}`);
12 | const getProduct = response.data.product;
13 | setLoader(false);
14 | setProduct(getProduct);
15 | } catch (error) {
16 | console.log("Error from getProductById.js ", error);
17 | }
18 | })(); //IIFE - Immediately Invoked Function
19 | }, [productId]);
20 |
21 | return { loader, product };
22 | };
23 |
--------------------------------------------------------------------------------
/src/pages/ErrorPage.jsx:
--------------------------------------------------------------------------------
1 | import { Navbar } from "../components/Navbar";
2 | import { Link } from "react-router-dom";
3 | import "../styles/pages/errorPage.css";
4 | export const ErrorPage = () => {
5 | return (
6 | <>
7 |
8 |
9 |
10 |
11 |
16 |
17 | Go to Home Page
18 |
19 |
20 |
21 | >
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/src/styles/utilities/variables.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --transparent-grey: rgba(255, 255, 255, 0.432);
3 | --tree-green: #74bb98;
4 | --box-shadow-1: rgba(0, 0, 0, 0.35) 0px 5px 15px;
5 | --box-shadow-2: rgba(0, 0, 0, 0.25) 0px 54px 55px,
6 | rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px,
7 | rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
8 | --dark-blue: #00008b;
9 | --dark-brown: rgb(116, 30, 30);
10 | --box-shadow-right: 15px 0 15px -15px #333;
11 | --button-shadow-1: 0 0 20px #eee;
12 | --purple: rgb(170, 37, 170);
13 | }
14 | .white-color {
15 | color: #fff;
16 | }
17 | .red-color {
18 | color: red;
19 | }
20 |
21 | .purple-color {
22 | color: var(--purple-color);
23 | }
24 | .primary-color {
25 | color: var(--primary);
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/FeaturedCategories.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import {Link} from "react-router-dom";
3 | import {useProduct} from "../context/product-context"
4 |
5 | function FeaturedCategories({imgSrc,categoryTitle}){
6 | const {dispatchFilters} = useProduct();
7 | return(
8 |
9 |
10 |
dispatchFilters({type:"HOME-CATEGORIES-LINK",payload:categoryTitle})}>
11 |
{categoryTitle}
12 |
17 |
18 |
19 | )
20 | }
21 |
22 | export {FeaturedCategories};
23 |
--------------------------------------------------------------------------------
/src/pages/OrderSuccessPage.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import {
3 | Navbar,
4 | Footer,
5 | // HorizontalCard,
6 | } from "../components/allComponents";
7 | import "../styles/pages/cart.css";
8 | import { OrderSuccessCard } from "../components/OrderSuccessCard";
9 | import { useParams } from "react-router-dom";
10 | export const OrderSuccessPage = () => {
11 | let { paymentId } = useParams();
12 | return (
13 | <>
14 |
15 | {/* */}
16 |
17 |
18 | Order Confirmation Page
19 |
20 |
21 | { }
22 |
23 |
24 |
25 |
26 | >
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
18 |
19 | AgroStores - An E-Commerce App for buying Agro Products
20 |
21 |
22 | You need to enable JavaScript to run this app.
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/pages/WishList.js:
--------------------------------------------------------------------------------
1 |
2 | import { Navbar,Footer,ProductCard } from "../components/allComponents"
3 | import { useWishList } from "../context/wishlist-context";
4 | import "../styles/pages/wishlist.css"
5 |
6 | export const WishList = () =>{
7 | const {wishListState} = useWishList();
8 | const displayWishListProducts = wishListState.wishlistItems;
9 | const wishListCount = displayWishListProducts.length;
10 | return(<>
11 |
12 |
13 | {/* */}
14 |
15 |
{wishListCount>0 ? `My Wishlist (${wishListCount})`:`Your Wishlist is Empty`}
16 |
17 | {displayWishListProducts.map((item)=>
)}
18 |
19 |
20 |
21 | >)
22 | }
--------------------------------------------------------------------------------
/src/styles/components/loader.css:
--------------------------------------------------------------------------------
1 | .lds-ring {
2 | display: inline-block;
3 | position: absolute;
4 | left: 45rem;
5 | top: 20rem;
6 | width: 5rem;
7 | height: 5rem;
8 | }
9 | .lds-ring div {
10 | box-sizing: border-box;
11 | display: block;
12 | position: absolute;
13 | width: 4rem;
14 | height: 4rem;
15 | margin: 8px;
16 | border: 8px solid rgb(35, 65, 199);
17 | border-radius: 50%;
18 | animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
19 | border-color: rgb(35, 65, 199) transparent transparent transparent;
20 | }
21 | .lds-ring div:nth-child(1) {
22 | animation-delay: -0.45s;
23 | }
24 | .lds-ring div:nth-child(2) {
25 | animation-delay: -0.3s;
26 | }
27 | .lds-ring div:nth-child(3) {
28 | animation-delay: -0.15s;
29 | }
30 | @keyframes lds-ring {
31 | 0% {
32 | transform: rotate(0deg);
33 | }
34 | 100% {
35 | transform: rotate(360deg);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/reducers/cartReducer.js:
--------------------------------------------------------------------------------
1 | export const cartReducer = (state, { type, payload }) => {
2 | switch (type) {
3 | case "ADD-TO-CART":
4 | case "UPDATE-CART":
5 | return {
6 | ...state,
7 | cartItems: payload,
8 | };
9 | case "REMOVE-FROM-CART":
10 | return { ...state, cartItems: payload };
11 | case "TOGGLE-QUANTITY":
12 | return { ...state, cartItems: toggleQuantity(state, payload) };
13 | case "CLEAR-CART":
14 | return cartInitialState;
15 | default:
16 | state;
17 | }
18 | };
19 |
20 | export const cartInitialState = {
21 | cartItems: [],
22 | totalPrice: 0,
23 | totalDiscountPrice: 0,
24 | totalQuantity: 0,
25 | };
26 |
27 | function toggleQuantity(state, payload) {
28 | for (let i = 0; i < state.cartItems.length; i++) {
29 | if (state.cartItems[i]._id == payload._id) {
30 | state.cartItems[i].quantity = payload.quantityValue;
31 | }
32 | }
33 | return [...state.cartItems];
34 | }
35 |
--------------------------------------------------------------------------------
/src/backend/controllers/ProductController.js:
--------------------------------------------------------------------------------
1 | import { Response } from "miragejs";
2 |
3 | /**
4 | * All the routes related to Product are present here.
5 | * These are Publicly accessible routes.
6 | * */
7 |
8 | /**
9 | * This handler handles gets all products in the db.
10 | * send GET Request at /api/products
11 | * */
12 |
13 | export const getAllProductsHandler = function () {
14 | return new Response(200, {}, { products: this.db.products });
15 | };
16 |
17 | /**
18 | * This handler handles gets all products in the db.
19 | * send GET Request at /api/user/products/:productId
20 | * */
21 |
22 | export const getProductHandler = function (schema, request) {
23 | const productId = request.params.productId;
24 | try {
25 | const product = schema.products.findBy({ _id: productId });
26 | return new Response(200, {}, { product });
27 | } catch (error) {
28 | return new Response(
29 | 500,
30 | {},
31 | {
32 | error,
33 | }
34 | );
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/src/components/Footer.js:
--------------------------------------------------------------------------------
1 |
2 | import "../styles/layouts/footer.css"
3 |
4 | export const Footer = ()=>{
5 | return(
6 |
7 | Made by Kedar Kulkarni
8 |
22 | © 2022 AgroStores
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/src/styles/components/editAddressModal.css:
--------------------------------------------------------------------------------
1 | .edit-address-background {
2 | position: fixed;
3 | top: 0;
4 | left: 0;
5 | width: 100%;
6 | height: 100%;
7 | background-color: rgba(0, 0, 0, 0.6);
8 | place-items: center;
9 | display: none;
10 | z-index: 2;
11 | }
12 |
13 | .edit-address-container {
14 | font-family: var(--roboto-font);
15 | width: 400px;
16 | min-height: 5rem;
17 | max-height: max-content;
18 | gap: 1rem;
19 | background-color: var(--white);
20 | box-shadow: var(--box-shadow-1);
21 | border-radius: var(--radius-md);
22 | overflow-y: auto;
23 | max-height: 100%;
24 | }
25 |
26 | .action-buttons {
27 | margin-left: auto;
28 | gap: 1rem;
29 | cursor: pointer;
30 | }
31 | .show-edit-box {
32 | display: grid;
33 | }
34 | .hide-edit-box {
35 | display: none;
36 | }
37 |
38 | .btn-fill-dummy-address {
39 | margin: 0;
40 | margin-left: auto;
41 | width: 9.5rem;
42 | }
43 |
44 | @media only screen and (max-width: 400px) {
45 | .edit-address-container {
46 | width: 320px;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/styles/pages/forgotpassword.css:
--------------------------------------------------------------------------------
1 |
2 | .reset-pwd-container{
3 | width: 100%;
4 | font-family: var(--lato-font);
5 | background-color: var(--primary-transparent);
6 | margin: 0;
7 | min-height: 100vh;
8 | max-height: max-content;
9 | }
10 | .reset-pwd-card{
11 | display: flex;
12 | flex-direction: column;
13 | width: 28rem;
14 | border-radius: var(--radius-md);
15 | background-color: var(--white);
16 | box-shadow: var(--box-shadow-1);
17 | height: fit-content;
18 | padding: var(--size-sm) var(--size-md) ;
19 | margin: 2rem 0;
20 | }
21 |
22 | .reset-pwd-card input{
23 | height: var(--size-lg);
24 | font-size: 1.1rem;
25 | }
26 |
27 |
28 | .reset-pwd-card input:focus{
29 | border: 2px solid var(--primary);
30 | }
31 |
32 | .reset-pwd-card .btn{
33 | padding: var(--size-sm);
34 | border: none;
35 | justify-content: center;
36 | width: 100%;
37 | margin: 0.5rem;
38 | }
39 |
40 | @media only screen and (max-width: 800px){
41 | .reset-pwd-card{
42 | width: fit-content;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/styles/components/mobileMenu.css:
--------------------------------------------------------------------------------
1 | .mobile-menu {
2 | display: flex;
3 | flex-direction: column;
4 | background-color: var(--lightest-green);
5 | box-shadow: var(--box-shadow-2);
6 | position: fixed;
7 | top: 0;
8 | left: 0;
9 | min-height: 100vh;
10 | height: 100%;
11 | transform: translate(-100%);
12 | transition: transform 0.3s linear;
13 | z-index: 4;
14 | }
15 | .mobile-menu.show-menu {
16 | transform: translate(0);
17 | }
18 |
19 | .mobile-menu-head {
20 | display: flex;
21 | align-items: center;
22 | }
23 |
24 | .mobile-menu-icon {
25 | font-size: 3rem;
26 | color: var(--primary);
27 | cursor: pointer;
28 | }
29 |
30 | .mobile-logo {
31 | font-family: var(--acme-font);
32 | color: var(--primary);
33 | margin-left: 10px;
34 | font-size: 2rem;
35 | }
36 |
37 | .mobile-menu-close {
38 | font-size: 3rem;
39 | color: var(--primary);
40 | padding-left: 2rem;
41 | cursor: pointer;
42 | }
43 |
44 | @media screen and (max-width: 600px) {
45 | .mobile-header {
46 | display: flex;
47 | gap: 2rem;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./App";
4 | import { makeServer } from "./server";
5 | import { BrowserRouter as Router } from "react-router-dom";
6 | import { ProductProvider } from "./context/product-context";
7 | import { CartProvider } from "./context/cart-context";
8 | import { WishListProvider } from "./context/wishlist-context";
9 | import { AuthProvider } from "./context/auth-context";
10 | import { AddressProvider } from "./context/address-context";
11 |
12 | // Call make Server
13 | makeServer();
14 |
15 | ReactDOM.render(
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | ,
31 | ,
32 | document.getElementById("root")
33 | );
34 |
--------------------------------------------------------------------------------
/src/pages/Checkout.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import {
3 | Navbar,
4 | Footer,
5 | // HorizontalCard,
6 | CartPriceCard,
7 | } from "../components/allComponents";
8 | import "../styles/pages/cart.css";
9 | import { useCart } from "../context/cart-context";
10 | import { AddressCard } from "../components/AddressCard";
11 | import { EditAddressModal } from "../components/EditAddressModal";
12 |
13 | export const Checkout = () => {
14 | const { cartState } = useCart();
15 |
16 | return (
17 | <>
18 |
19 | {/* */}
20 |
21 |
22 | {cartState.cartItems.length > 0
23 | ? `Checkout Page`
24 | : `No Products for Checkout`}
25 |
26 |
27 | {cartState.cartItems.length > 0 &&
}
28 |
29 | {cartState.cartItems.length > 0 &&
}
30 |
31 |
32 |
33 |
34 | >
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/src/styles/pages/searchPage.css:
--------------------------------------------------------------------------------
1 | .search-page-body {
2 | font-family: var(--lato-font);
3 | background-color: var(--lightest-green);
4 | padding-bottom: 1rem;
5 | margin: 0;
6 | min-height: 100vh;
7 | max-height: max-content;
8 | }
9 |
10 | .search-container-main {
11 | justify-content: center;
12 | width: 100%;
13 | gap: 1rem;
14 | flex-wrap: wrap;
15 | }
16 |
17 | #search-mobile-bar,
18 | .search-mobile-field {
19 | border-radius: 5px;
20 | border: 2px solid var(--primary);
21 | }
22 |
23 | .search-mobile-field {
24 | position: relative;
25 | margin-left: 15vw;
26 | margin-right: 15vw;
27 | width: 70vw;
28 | background-color: var(--white);
29 | margin-bottom: 1rem;
30 | }
31 | #search-mobile-icon {
32 | position: absolute;
33 | top: 20%;
34 | right: 2%;
35 | }
36 |
37 | #search-mobile-bar {
38 | border: none;
39 | height: 2.5rem;
40 | width: 85%;
41 | font-size: 1.2rem;
42 | padding: 0rem 0.5rem;
43 | }
44 | @media only screen and (max-width: 2560px) {
45 | .search-mobile-field {
46 | display: none;
47 | }
48 | }
49 | @media only screen and (max-width: 900px) {
50 | .search-mobile-field {
51 | display: block;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/backend/controllers/CategoryController.js:
--------------------------------------------------------------------------------
1 | import { Response } from "miragejs";
2 |
3 | /**
4 | * All the routes related to Category are present here.
5 | * These are Publicly accessible routes.
6 | * */
7 |
8 | /**
9 | * This handler handles gets all categories in the db.
10 | * send GET Request at /api/categories
11 | * */
12 |
13 | export const getAllCategoriesHandler = function () {
14 | try {
15 | return new Response(200, {}, { categories: this.db.categories });
16 | } catch (error) {
17 | return new Response(
18 | 500,
19 | {},
20 | {
21 | error,
22 | }
23 | );
24 | }
25 | };
26 |
27 | /**
28 | * This handler handles gets all categories in the db.
29 | * send GET Request at /api/user/category/:categoryId
30 | * */
31 |
32 | export const getCategoryHandler = function (schema, request) {
33 | const categoryId = request.params.categoryId;
34 | try {
35 | const category = schema.categories.findBy({ _id: categoryId });
36 | return new Response(200, {}, { category });
37 | } catch (error) {
38 | new Response(
39 | 500,
40 | {},
41 | {
42 | error,
43 | }
44 | );
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "e-commerce-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "axios": "^0.21.4",
7 | "dayjs": "^1.10.7",
8 | "jwt-decode": "^3.1.2",
9 | "jwt-encode": "^1.0.1",
10 | "miragejs": "^0.1.41",
11 | "mockman-js": "^1.0.5",
12 | "react": "^17.0.2",
13 | "react-debounce-input": "^3.3.0",
14 | "react-dom": "^17.0.2",
15 | "react-router-dom": "^6.2.2",
16 | "react-scripts": "4.0.3",
17 | "react-toastify": "^8.2.0",
18 | "uuid": "^8.3.2",
19 | "web-vitals": "^0.2.4"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | },
39 | "devDependencies": {
40 | "eslint": "^7.32.0",
41 | "eslint-plugin-react": "^7.29.4",
42 | "eslint-plugin-react-hooks": "^4.4.0"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/components/OrderSuccessCard.jsx:
--------------------------------------------------------------------------------
1 | import "../styles/components/orderCard.css";
2 | import { Link } from "react-router-dom";
3 | export const OrderSuccessCard = ({ paymentId }) => {
4 | function getRandomInt(max) {
5 | return Math.floor(Math.random() * max);
6 | }
7 | return (
8 | <>
9 |
10 |
14 |
Your Order Has Been Placed Successfully
15 |
16 | Order ID: {"OD" + getRandomInt(9999999999)}
17 |
18 |
Payment ID: {paymentId}
19 |
20 | You will receive order details soon on your email
21 |
22 |
23 |
24 | Continue Shopping{" "}
25 |
shopping_basket
26 |
27 |
28 | >
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/src/utils/filterReducer.js:
--------------------------------------------------------------------------------
1 |
2 | export const filterReducer = (state, {type,payload}) => {
3 | switch (type) {
4 | case "SORT":
5 | return { ...state, sortBy: payload };
6 | case "REMOVE-OUT-OF-STOCK":
7 | return { ...state, removeOutOfStock: !state.removeOutOfStock };
8 | case "PRICE-RANGE-FILTER":
9 | return { ...state, maxPriceRange: payload };
10 | case 'RATINGS':
11 | return { ...state, ratings: payload };
12 | case 'CATEGORIES':
13 | return { ...state, categoryNames: getTickedNames(state,payload) }
14 | case 'HOME-CATEGORIES-LINK':
15 | return { ...state, categoryNames: [payload] }
16 | case 'CLEAR-FILTERS':
17 | return filterInitialState;
18 |
19 |
20 | default: state;
21 | }
22 | };
23 |
24 | export const filterInitialState = {
25 | sortBy: null,
26 | removeOutOfStock: false,
27 | maxPriceRange: 3000,
28 | ratings:0,
29 | categoryNames:[]
30 |
31 | };
32 |
33 | const getTickedNames = (state,payload)=>{
34 | let stateCategoryNames =state.categoryNames;
35 | if(stateCategoryNames.includes(payload)){
36 | return stateCategoryNames.filter(name=>name!==payload);
37 | }
38 | else{
39 | return [...state.categoryNames,payload];
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/utils/filterProducts.js:
--------------------------------------------------------------------------------
1 |
2 | export const filterProducts = (state, productList) => {
3 | //Filter-Products-by-Categories
4 | if(state.categoryNames.length>0){
5 | let prodList =[] ;
6 | state.categoryNames.map(name=>
7 | prodList = prodList.concat(productList.filter((item) => item.categoryName === name)))
8 | productList = [...prodList];
9 | }
10 |
11 | //Below for Price Range filter
12 | productList = productList.filter(
13 | (item) => Number(item.price) < Number(state.maxPriceRange)
14 | );
15 | //Ratings filter
16 | productList = productList.filter(
17 | (item) => (item.rating) >= (state.ratings)
18 | );
19 | //Sort-By-Price
20 | if (state.sortBy === "PRICE-LOW-TO-HIGH") {
21 | productList = productList.sort((a, b) => a.price - b.price);
22 | }
23 | if (state.sortBy === "PRICE-HIGH-TO-LOW") {
24 | productList = productList.sort((a, b) => b.price - a.price);
25 | }
26 | //Hide-Out-Of-Stock
27 | if (state.removeOutOfStock) {
28 | productList = productList.filter((item) => !item.isOutOfStock);
29 | }
30 |
31 | return productList;
32 | };
33 |
--------------------------------------------------------------------------------
/src/pages/Cart.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import {
3 | Navbar,
4 | Footer,
5 | HorizontalCard,
6 | CartPriceCard,
7 | } from "../components/allComponents";
8 | import "../styles/pages/cart.css";
9 | import { useCart } from "../context/cart-context";
10 |
11 | export const Cart = () => {
12 | const { cartState } = useCart();
13 |
14 | return (
15 | <>
16 |
17 | {/* */}
18 |
19 |
20 | {cartState.cartItems.length > 0
21 | ? `Shopping Cart`
22 | : `Your Cart is Empty`}
23 |
24 |
25 |
26 | {/* */}
27 |
28 | {/* */}
29 | {cartState.cartItems.length > 0 &&
30 | cartState.cartItems.map((item) => (
31 |
32 | ))}
33 |
34 | {/* */}
35 | {cartState.cartItems.length > 0 &&
}
36 |
37 |
38 |
39 |
40 | >
41 | );
42 | };
43 |
--------------------------------------------------------------------------------
/src/services/wishlistServices.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { toast } from "react-toastify";
3 |
4 | export const addItemToWishlist = async ({
5 | auth,
6 | itemDetails,
7 | dispatchWishList,
8 | }) => {
9 | try {
10 | const response = await axios({
11 | url: "/api/user/wishlist",
12 | method: "post",
13 | headers: {
14 | authorization: auth.token,
15 | },
16 | data: {
17 | product: itemDetails,
18 | },
19 | });
20 |
21 | dispatchWishList({
22 | type: "ADD-TO-WISHLIST",
23 | payload: response.data.wishlist,
24 | });
25 | toast.success("Item Added to Wishlist");
26 | } catch (error) {
27 | console.log("Add Item to Wishlist Error", error);
28 | toast.error("Error to add Item in Wishlist");
29 | }
30 | };
31 |
32 | export const removeItemFromWishlist = async ({
33 | auth,
34 | itemId,
35 | dispatchWishList,
36 | }) => {
37 | try {
38 | const response = await axios.delete(`/api/user/wishlist/${itemId}`, {
39 | headers: { authorization: auth.token },
40 | });
41 |
42 | dispatchWishList({
43 | type: "REMOVE-FROM-WISHLIST",
44 | payload: response.data.wishlist,
45 | });
46 | toast.success("Item Removed from Wishlist");
47 | } catch (error) {
48 | console.log("Remove Item from Wishlist Error", error);
49 | toast.error("Error to remove Item from Wishlist");
50 | }
51 | };
52 |
--------------------------------------------------------------------------------
/src/styles/pages/profile.css:
--------------------------------------------------------------------------------
1 | .Profile {
2 | font-family: var(--acme-font);
3 | background-color: var(--primary-transparent);
4 |
5 | /* background-color: lightslategray; */
6 | min-height: 100vh;
7 | max-height: max-content;
8 | }
9 |
10 | .profile-card-container {
11 | grid-area: myProfile;
12 | display: flex;
13 | justify-content: center;
14 | }
15 |
16 | .profile-card {
17 | display: flex;
18 | flex-direction: column;
19 | width: 28rem;
20 | border-radius: var(--radius-md);
21 | background-color: rgb(146, 212, 208);
22 | box-shadow: var(--box-shadow-2);
23 | height: fit-content;
24 | padding: var(--size-sm) var(--size-md);
25 | margin: 3rem 0;
26 | }
27 |
28 | .avatar {
29 | margin: 1rem 9rem;
30 | }
31 |
32 | .btn-show-address,
33 | .btn-add-address {
34 | width: 12rem;
35 | }
36 | .btn-delete-address,
37 | .btn-edit-address,
38 | .show-hide-address-icon {
39 | cursor: pointer;
40 | border-radius: 15px;
41 | padding: 5px;
42 | }
43 |
44 | .btn-delete-address:hover,
45 | .btn-edit-address:hover,
46 | .show-hide-address-icon:hover {
47 | cursor: pointer;
48 | background-color: var(--success);
49 | }
50 | @media screen and (max-width: 800px) {
51 | .profile-page-container {
52 | display: flex;
53 | flex-direction: column;
54 | }
55 | .avatar {
56 | margin: 1rem 20vw;
57 | }
58 | .profile-card {
59 | width: 80vw;
60 | margin-bottom: 5rem;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/styles/pages/signup.css:
--------------------------------------------------------------------------------
1 | .signup-container {
2 | width: 100%;
3 | font-family: var(--lato-font);
4 | background-color: var(--primary-transparent);
5 | margin: 0;
6 | min-height: 100vh;
7 | max-height: max-content;
8 | }
9 | .signup-card {
10 | display: flex;
11 | flex-direction: column;
12 | width: 28rem;
13 | border-radius: 10px;
14 | background-color: var(--white);
15 | box-shadow: var(--box-shadow-1);
16 | height: fit-content;
17 | padding: 1rem 1.5rem;
18 | margin: 2rem;
19 | position: relative;
20 | }
21 | input[type="checkbox"] {
22 | width: 1rem;
23 | height: 1rem;
24 | accent-color: var(--primary);
25 | }
26 | .signup-card input {
27 | height: 1.5rem;
28 | font-size: 1.1rem;
29 | }
30 |
31 | .signup-pwd-show-icon {
32 | cursor: pointer;
33 | }
34 |
35 | .signup-card input:focus {
36 | border: 2px solid var(--primary);
37 | }
38 |
39 | .signup-card .btn-signin,
40 | .signup-card .btn-create-account {
41 | padding: 1rem;
42 | border: none;
43 | justify-content: center;
44 | width: 100%;
45 | margin: 0.5rem;
46 | }
47 |
48 | .password-item,
49 | .confirm-password-item {
50 | position: relative;
51 | }
52 | .signup-pwd-show-icon {
53 | position: absolute;
54 | top: 1.75rem;
55 | right: 5px;
56 | }
57 |
58 | .btn-fill-dummy-data {
59 | margin: 0;
60 | margin-left: auto;
61 | width: 9.5rem;
62 | }
63 |
64 | @media only screen and (max-width: 800px) {
65 | .signup-card {
66 | width: fit-content;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/pages/ForgotPassword.js:
--------------------------------------------------------------------------------
1 | import { Navbar, Footer } from "../components/allComponents";
2 | import { Link } from "react-router-dom";
3 | import "../styles/pages/forgotpassword.css"
4 |
5 | export const ForgotPassword = ()=>{
6 | return(
7 | <>
8 |
9 | {/* Main Container */}
10 |
11 |
12 | {/* Heading */}
13 |
Forgot Password
14 | {/* Email-id */}
15 |
16 | Email address
17 |
23 |
24 | {/* Buttons */}
25 |
Reset Password
26 |
27 | Create New Account
chevron_right
28 |
29 |
30 |
31 |
32 | >
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/src/styles/pages/login.css:
--------------------------------------------------------------------------------
1 | .login-container {
2 | width: 100%;
3 | font-family: var(--lato-font);
4 | background-color: var(--primary-transparent);
5 | margin: 0;
6 | min-height: 100vh;
7 | max-height: max-content;
8 | }
9 | .login-card {
10 | display: flex;
11 | flex-direction: column;
12 | width: 28rem;
13 | border-radius: var(--radius-md);
14 | background-color: var(--white);
15 | box-shadow: var(--box-shadow-1);
16 | height: fit-content;
17 | padding: var(--size-sm) var(--size-md);
18 | margin: 2rem 0;
19 | }
20 |
21 | .login-card input {
22 | height: var(--size-lg);
23 | font-size: 1.1rem;
24 | }
25 |
26 | .pwd-show-icon {
27 | cursor: pointer;
28 | }
29 |
30 | .login-card input:focus {
31 | border: 2px solid var(--primary);
32 | }
33 |
34 | .login-card .btn {
35 | padding: var(--size-sm);
36 | border: none;
37 | justify-content: center;
38 | width: 100%;
39 | margin: 0.5rem;
40 | }
41 |
42 | .password-item {
43 | position: relative;
44 | }
45 | .login-pwd-show-icon {
46 | position: absolute;
47 | top: 2rem;
48 | right: 5px;
49 | }
50 | .forgot-password {
51 | color: var(--primary);
52 | }
53 |
54 | .login-card .btn-outline {
55 | padding: var(--size-sm);
56 | justify-content: center;
57 | width: 100%;
58 | margin: 0.5rem;
59 | border: 2px solid var(--primary);
60 | }
61 |
62 | input[type="checkbox"] {
63 | width: 1rem;
64 | height: 1rem;
65 | accent-color: var(--primary);
66 | }
67 |
68 | @media only screen and (max-width: 800px) {
69 | .login-card {
70 | width: fit-content;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/styles/pages/singleProductPage.css:
--------------------------------------------------------------------------------
1 | .single-product-page {
2 | font-family: var(--lato-font);
3 | background-color: var(--primary-transparent);
4 | min-height: 100vh;
5 | max-height: max-content;
6 | }
7 |
8 | .product-box,
9 | .product-description-box {
10 | width: 80%;
11 | /* border: 1px solid black; */
12 | background-color: var(--white);
13 | box-shadow: var(--box-shadow-simple);
14 | margin-left: 8rem;
15 | margin-top: 3rem;
16 | gap: 2rem;
17 | padding: 2rem;
18 | }
19 | .product-description-box {
20 | margin-bottom: 10rem;
21 | }
22 | .single-product-page .product-img {
23 | width: 17rem;
24 | height: 25rem;
25 | }
26 |
27 | .single-product-page .card-ratings {
28 | width: 3.8rem;
29 | }
30 |
31 | .single-product-page .card-pricing {
32 | font-size: 1.5rem;
33 | }
34 | .product-description {
35 | width: 25rem;
36 | }
37 | .product-info {
38 | /* border: 1px solid black; */
39 | margin-left: auto;
40 | gap: 2rem;
41 | }
42 | .product-details {
43 | gap: 1rem;
44 | }
45 | @media screen and (max-width: 800px) {
46 | .product-box {
47 | flex-direction: column;
48 | margin-left: 2rem;
49 | }
50 | .product-description-box {
51 | flex-direction: column;
52 | margin-left: 2rem;
53 | }
54 | .product-description {
55 | width: 15rem;
56 | }
57 | .single-product-page .product-img {
58 | width: 12rem;
59 | height: 20rem;
60 | }
61 | .single-product-title {
62 | font-size: 1.5rem;
63 | }
64 | }
65 | @media screen and (max-width: 370px) {
66 | .product-box,
67 | .product-description-box {
68 | padding: 10px;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/styles/layouts/navbar.css:
--------------------------------------------------------------------------------
1 | nav a {
2 | text-decoration: none;
3 | color: var(--primary);
4 | }
5 |
6 | nav {
7 | flex-wrap: wrap;
8 | height: 5rem;
9 | /* align-items: center; */
10 | justify-content: space-around;
11 | padding: 0.5rem 0;
12 | background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
13 | font-family: var(--acme-font);
14 | }
15 |
16 | #search-bar,
17 | .search-field {
18 | border-radius: 5px;
19 | border: 2px solid var(--primary);
20 | }
21 |
22 | .search-field {
23 | position: relative;
24 | width: 18.75rem;
25 | background-color: var(--white);
26 | }
27 |
28 | .dark-brown-color {
29 | color: var(--dark-brown);
30 | }
31 |
32 | #search-icon {
33 | position: absolute;
34 | top: 20%;
35 | right: 2%;
36 | }
37 |
38 | #search-bar {
39 | border: none;
40 | height: 2.5rem;
41 | width: 85%;
42 | font-size: 1.2rem;
43 | padding: 0rem 0.5rem;
44 | }
45 | /* .navbar {
46 | position: relative;
47 | } */
48 |
49 | .btn-login,
50 | .btn-logout {
51 | width: 6rem;
52 | font-size: 1.1rem;
53 | padding: 0.5rem 1.75rem;
54 | }
55 | .nav-pills i {
56 | cursor: pointer;
57 | font-size: 2rem;
58 | }
59 | .mobile-header {
60 | display: none;
61 | }
62 |
63 | @media only screen and (max-width: 900px) {
64 | nav {
65 | height: min-content;
66 | justify-content: flex-start;
67 | }
68 | #search-bar,
69 | .search-field {
70 | display: none;
71 | }
72 | .nav-pills {
73 | display: none;
74 | }
75 | .mobile-header {
76 | display: flex;
77 | gap: 1rem;
78 | margin-left: 1rem;
79 | align-items: center;
80 | }
81 | .nav-brand {
82 | display: none;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/services/cartServices.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { toast } from "react-toastify";
3 |
4 | export const addItemToCart = async ({ auth, itemDetails, dispatchCart }) => {
5 | try {
6 | const response = await axios({
7 | url: "/api/user/cart",
8 | method: "post",
9 | headers: {
10 | authorization: auth.token,
11 | },
12 | data: {
13 | product: itemDetails,
14 | },
15 | });
16 |
17 | dispatchCart({
18 | type: "ADD-TO-CART",
19 | payload: response.data.cart,
20 | });
21 | toast.success("Item Added to Cart");
22 | } catch (error) {
23 | console.log("Add Item to Cart Error", error);
24 | toast.error("Error to add Item in Cart");
25 | }
26 | };
27 |
28 | export const removeItemFromCart = async ({ auth, itemId, dispatchCart }) => {
29 | try {
30 | const response = await axios.delete(`/api/user/cart/${itemId}`, {
31 | headers: { authorization: auth.token },
32 | });
33 |
34 | dispatchCart({
35 | type: "REMOVE-FROM-CART",
36 | payload: response.data.cart,
37 | });
38 | toast.success("Item Removed from Cart");
39 | } catch (error) {
40 | console.log("Remove Item from Cart Error", error);
41 | toast.error("Error to remove Item from Cart");
42 | }
43 | };
44 | //clears cart after payment.
45 | export const clearCart = async ({ auth, dispatchCart }) => {
46 | try {
47 | await axios.delete(`/api/user/clearCart`, {
48 | headers: { authorization: auth.token },
49 | });
50 | dispatchCart({ type: "CLEAR-CART" });
51 | } catch (error) {
52 | console.log("Clear Cart Error", error);
53 | toast.error("Error to Clear Cart");
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import { Navbar,Footer,FeaturedCategories } from "../components/allComponents";
2 | import "../styles/pages/home.css"
3 | import { GetFeaturedCategories } from "../services/getFeaturedCategories";
4 | import {Link} from "react-router-dom";
5 |
6 | export const Home = ()=>{
7 |
8 | return(
9 | <>
10 |
11 |
12 | {/* Header Image */}
13 |
14 |
15 |
16 | One Stop Solution for your farming needs
17 |
18 |
19 | Buy Fertilizers, Insectisides, Seeds & other Agriculture Products
20 |
21 |
25 | Shop Now
26 |
27 |
28 |
29 |
Featured Categories
30 |
31 |
32 | {GetFeaturedCategories().map((item) =>
33 |
34 | )}
35 |
36 |
37 |
38 | >
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/src/styles/pages/cart.css:
--------------------------------------------------------------------------------
1 | /* Main Wrapper Container */
2 | .cart-body {
3 | font-family: var(--lato-font);
4 | background-color: var(--lightest-green);
5 | padding-bottom: 1rem;
6 | margin: 0;
7 | min-height: 100vh;
8 | max-height: max-content;
9 | }
10 | .cart-heading {
11 | color: var(--primary);
12 | }
13 |
14 | .cart-container-main {
15 | flex-wrap: wrap;
16 | width: 100%;
17 | gap: 2rem;
18 | justify-content: center;
19 | margin-bottom: 2rem;
20 | }
21 |
22 | /* Products List in cart */
23 | .final-product-list {
24 | width: 25rem;
25 | gap: 1rem;
26 | }
27 |
28 | #quantity {
29 | padding: 5px;
30 | font-size: 1rem;
31 | font-weight: bold;
32 | width: 2rem;
33 | margin: 5px;
34 | }
35 |
36 | .heart-icon {
37 | position: absolute;
38 | top: 0.5rem;
39 | right: 5px;
40 | font-size: 1.6rem;
41 | cursor: pointer;
42 | background-color: var(--lightest-green);
43 | padding: 5px;
44 | border-radius: 50%;
45 | color: var(--danger);
46 | font-weight: bold;
47 | }
48 |
49 | /* Pricing Table */
50 | .price-summary {
51 | background-color: var(--white);
52 | border-radius: 5px;
53 | width: 25rem;
54 | height: fit-content;
55 | padding: 1rem;
56 | box-shadow: var(--box-shadow-simple);
57 | }
58 | .price-item {
59 | justify-content: space-between;
60 | }
61 |
62 | .btn-place-order {
63 | padding: 1rem;
64 | justify-content: center;
65 | margin: 1rem 0.5rem;
66 | letter-spacing: 2px;
67 | }
68 |
69 | .total-amount {
70 | color: var(--primary);
71 | letter-spacing: 1px;
72 | }
73 |
74 | .note {
75 | color: var(--success);
76 | }
77 | .note-free-delivery {
78 | color: var(--purple);
79 | }
80 |
81 | .btn-payment {
82 | cursor: pointer;
83 | }
84 |
--------------------------------------------------------------------------------
/src/backend/db/categories.js:
--------------------------------------------------------------------------------
1 | import { v4 as uuid } from "uuid";
2 |
3 | /**
4 | * Category Database can be added here.
5 | * You can add category of your wish with different attributes
6 | * */
7 |
8 | export const categories = [
9 | {
10 | _id: uuid(),
11 | categoryName: "Fertilizers",
12 | description:"a natural or chemical substance that is put on land or soil to make plants grow better",
13 | imgSrc:"https://res.cloudinary.com/dvuh4fz9d/image/upload/v1648390941/fertilizers1_hdthei.jpg",
14 | },
15 | {
16 | _id: uuid(),
17 | categoryName: "Pesticides",
18 | description:
19 | "a chemical substance that is used for killing bugs especially insects, that eat food crops",
20 | imgSrc:"https://res.cloudinary.com/dvuh4fz9d/image/upload/v1648390940/pesticides1_pveijq.jpg",
21 | },
22 | {
23 | _id: uuid(),
24 | categoryName: "Farming Tools",
25 | description:
26 | "Agricultural tools are instruments that are used in the fields to aid in the agricultural process.",
27 | imgSrc:"https://res.cloudinary.com/dvuh4fz9d/image/upload/v1648390940/tools_emqkuu.jpg",
28 | },
29 | {
30 | _id: uuid(),
31 | categoryName: "Seeds",
32 | description:
33 | "Vegetables & Fruits Seeds",
34 | imgSrc:"https://res.cloudinary.com/dvuh4fz9d/image/upload/v1648390941/seeds_tqx9ub.jpg",
35 | },
36 | {
37 | _id: uuid(),
38 | categoryName: "Safety Wearables",
39 | description: "Safety Wearables such as shoes, gloves etc",
40 | imgSrc:"https://res.cloudinary.com/dvuh4fz9d/image/upload/v1648395553/farmer-spraying-rice_720x480_p9nhyb.webp",
41 | },
42 | {
43 | _id: uuid(),
44 | categoryName: "Electronic Devices",
45 | description:"Sensors, testers, torch & gadgets for farming",
46 | imgSrc:"https://res.cloudinary.com/dvuh4fz9d/image/upload/v1648397135/probeInSoil_1_cropped_s81abe.jpg",
47 | },
48 | ];
49 |
--------------------------------------------------------------------------------
/src/routes/PageRoutes.js:
--------------------------------------------------------------------------------
1 | import { Home } from "../pages/Home";
2 | import { Login } from "../pages/Login";
3 | import { Signup } from "../pages/Signup";
4 | import { ForgotPassword } from "../pages/ForgotPassword";
5 | import { ProductListing } from "../pages/ProductListing";
6 | import { Cart } from "../pages/Cart";
7 | import { WishList } from "../pages/WishList";
8 | import { RequiresAuth } from "./RequiresAuth";
9 | import Mockman from "mockman-js";
10 |
11 | import { Routes, Route } from "react-router-dom";
12 | import { SingleProductPage } from "../pages/SingleProductPage";
13 | import { Checkout } from "../pages/Checkout";
14 | import { MyProfile } from "../pages/MyProfile";
15 | import { OrderSuccessPage } from "../pages/OrderSuccessPage";
16 | import { SearchPage } from "../pages/SearchPage";
17 | import { ErrorPage } from "../pages/ErrorPage";
18 |
19 | export const PageRoutes = () => {
20 | return (
21 |
22 | } />
23 | } />
24 | } />
25 | } />
26 | } />
27 | } />
28 | } />
29 | } />
30 | } />
31 |
32 | }>
33 | } />
34 | } />
35 | } />
36 | }
39 | />
40 |
41 | } />
42 |
43 | );
44 | };
45 |
--------------------------------------------------------------------------------
/src/components/MobileMenu.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 | import "../styles/components/mobileMenu.css";
3 | import { useAuth } from "../context/auth-context";
4 | export const MobileMenu = ({
5 | mobileMenuStatus,
6 | setMobileMenuStatus,
7 | logoutHandler,
8 | wishlistItemsCount,
9 | cartItemsCount,
10 | }) => {
11 | let mobileClassName = "mobile-menu";
12 | if (mobileMenuStatus) {
13 | mobileClassName = "mobile-menu show-menu";
14 | } else {
15 | mobileClassName = "mobile-menu";
16 | }
17 | const { auth } = useAuth();
18 | return (
19 |
20 |
21 |
22 |
23 | AgroStores
24 |
25 |
26 | setMobileMenuStatus((toggler) => !toggler)}
29 | >
30 | close
31 |
32 |
33 |
34 | Show All Products
35 |
36 |
37 | Search Products
38 |
39 |
40 | Cart ({cartItemsCount})
41 |
42 |
43 | Wishlist ({wishlistItemsCount})
44 |
45 |
46 |
47 | My Profile
48 |
49 | {auth.token ? (
50 |
51 | Logout
52 |
53 | ) : (
54 |
55 | Login
56 |
57 | )}
58 |
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/src/pages/ProductListing.js:
--------------------------------------------------------------------------------
1 | import {
2 | Navbar,
3 | Footer,
4 | FiltersBar,
5 | ProductCard,
6 | } from "../components/allComponents";
7 | import "../styles/pages/productlist.css";
8 | import { useProduct } from "../context/product-context";
9 | import { filterProducts } from "../utils/filterProducts";
10 | import { GetProducts } from "../services/getProducts";
11 | import { useState } from "react";
12 | import { Loader } from "../components/Loader";
13 |
14 | export const ProductListing = () => {
15 | const { filterState } = useProduct();
16 | let windowWidth = window.innerWidth;
17 |
18 | // GetProducts() is fetching Products from Backend
19 | const { loader, products } = GetProducts();
20 |
21 | //Sidebar Toggler for Mobile Devices
22 | const [toggler, setToggler] = useState(windowWidth > 800);
23 | const toggleSetter = () =>
24 | toggler === true ? setToggler(false) : setToggler(true);
25 |
26 | //Array list to display products
27 | const displayProducts = filterProducts(filterState, products);
28 |
29 | return (
30 | <>
31 |
32 |
33 |
34 | {/* Filters Side-Bar */}
35 | {toggler &&
}
36 |
37 | {/* Product Cards */}
38 |
39 |
{loader && }
40 |
41 | {!loader && displayProducts.length == 0 && "No Products Found"}
42 |
43 |
44 | {/* Card */}
45 | {displayProducts.map((item) => (
46 |
47 | ))}
48 |
49 |
50 | {/* Filter on Mobile Button */}
51 | toggleSetter()}
55 | >
56 |
57 | {toggler ? `filter_alt_off` : `filter_alt`}
58 |
59 |
60 |
61 | >
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/src/reducers/addressReducer.js:
--------------------------------------------------------------------------------
1 | import { v4 as uuid } from "uuid";
2 |
3 | const defaultAddress = {
4 | id: uuid(),
5 | name: "Kedar Kulkarni",
6 | flatName: "1103/B, Gopal House",
7 | area: "Area 1",
8 | landmark: "Near Maharaj Bhavan",
9 | city: "Mumbai",
10 | pincode: "4000072",
11 | state: "Maharashtra",
12 | country: "India",
13 | contact: "9898989898",
14 | };
15 |
16 | export const addressInitialState = {
17 | addressList: [defaultAddress],
18 | setEditBox: "hide-edit-box",
19 | isEditing: false,
20 | editData: null,
21 | addressSelectedId: null,
22 | };
23 |
24 | export const addressReducer = (state, { type, payload }) => {
25 | switch (type) {
26 | case "ADD-NEW-ADDRESS":
27 | return {
28 | ...state,
29 | setEditBox: "hide-edit-box",
30 | addressList: [...state.addressList, payload],
31 | };
32 | case "DELETE-ADDRESS":
33 | return {
34 | ...state,
35 | addressList: state.addressList.filter(
36 | (address) => address.id != payload
37 | ),
38 | };
39 | case "TOGGLE-ADDRESS-MODAL":
40 | return {
41 | ...state,
42 | setEditBox: payload,
43 | };
44 | case "OPEN-EDIT-ADDRESS":
45 | return {
46 | ...state,
47 | isEditing: true,
48 | setEditBox: "show-edit-box",
49 | editData: payload,
50 | addressList: updateAddress(state, payload),
51 | };
52 | case "UPDATE-EDIT-ADDRESS":
53 | return {
54 | ...state,
55 | isEditing: false,
56 | setEditBox: "hide-edit-box",
57 | addressList: updateAddress(state, payload),
58 | };
59 | case "SET-EDIT-STATUS":
60 | return {
61 | ...state,
62 | isEditing: payload,
63 | };
64 | case "SET-ADDRESS-ID":
65 | return {
66 | ...state,
67 | addressSelectedId: payload,
68 | };
69 | case "CLEAR-ADDRESS":
70 | return addressInitialState;
71 |
72 | default:
73 | state;
74 | }
75 | };
76 |
77 | function updateAddress(state, payload) {
78 | let indexOfAddress = state.addressList.findIndex(
79 | (address) => address.id == payload.id
80 | );
81 | state.addressList[indexOfAddress] = payload;
82 | return state.addressList;
83 | }
84 |
--------------------------------------------------------------------------------
/src/pages/SearchPage.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import { Navbar, Footer } from "../components/allComponents";
3 | import "../styles/pages/cart.css";
4 | import { useState } from "react";
5 | import { ProductCard } from "../components/allComponents";
6 | import { GetProducts } from "../services/getProducts";
7 | import "../styles/pages/searchPage.css";
8 | import { DebounceInput } from "react-debounce-input";
9 |
10 | export const SearchPage = () => {
11 | const [searchQuery, setSearchQuery] = useState("");
12 | const { loader, products } = GetProducts();
13 | let displayProducts = [];
14 | if (!loader && searchQuery.length > 1) {
15 | displayProducts = products.filter((item) =>
16 | item.title.toLowerCase().includes(searchQuery)
17 | );
18 | }
19 | return (
20 | <>
21 |
22 | {/* */}
23 |
24 |
Search Page
25 | {/*
*/}
26 |
27 |
28 | search
29 |
30 | setSearchQuery(e.target.value)}
38 | />
39 |
40 |
41 |
42 | {searchQuery.length > 1
43 | ? `Search Query: "${searchQuery}"`
44 | : `Please type in search field to find products`}
45 |
46 |
47 | {!loader &&
48 | displayProducts.length == 0 &&
49 | searchQuery.length > 1 &&
50 | "No Products Found"}
51 |
52 |
53 | {!loader && (
54 |
55 | {displayProducts.map((item) => (
56 |
57 | ))}
58 |
59 | )}
60 |
61 |
62 |
63 | >
64 | );
65 | };
66 |
--------------------------------------------------------------------------------
/src/styles/pages/productlist.css:
--------------------------------------------------------------------------------
1 | .product-container-main {
2 | display: flex;
3 | font-family: var(--lato-font);
4 | padding: var(--size-sm);
5 | gap: var(--size-sm);
6 | background-color: var(--lightest-green);
7 | min-height: 100vh;
8 | max-height: max-content;
9 | }
10 |
11 | /* Filters-Sidebar */
12 | .filters-sidebar {
13 | background-color: var(--tree-green);
14 | border-radius: 5px;
15 | width: 20rem;
16 | height: fit-content;
17 | }
18 | .filters-sidebar a {
19 | color: var(--black);
20 | }
21 | .filter-clear-btn {
22 | cursor: pointer;
23 | }
24 | .filter-clear-btn:hover {
25 | text-decoration: underline;
26 | color: var(--white);
27 | }
28 |
29 | .filters-sidebar input {
30 | accent-color: var(--primary);
31 | }
32 |
33 | input[type="checkbox"],
34 | input[type="radio"] {
35 | width: 1rem;
36 | height: 1rem;
37 | cursor: pointer;
38 | }
39 |
40 | .filters-sidebar input[type="range"] {
41 | cursor: pointer;
42 | }
43 | .filters-sidebar label {
44 | cursor: pointer;
45 | }
46 | .filter-title,
47 | .filter-price {
48 | justify-content: space-between;
49 | }
50 |
51 | .price-slider {
52 | width: 12.5rem;
53 | }
54 |
55 | /* Products listing */
56 | .products-list {
57 | display: grid;
58 | width: 100%;
59 | grid-template-columns: repeat(auto-fit, 15rem);
60 | grid-auto-rows: min(30rem, 35rem); /* (30rem,auto/35)*/
61 | grid-gap: 1rem;
62 | justify-content: left;
63 | }
64 |
65 | .heart-icon-product-list {
66 | position: absolute;
67 | z-index: 3;
68 | top: 0.2rem;
69 | right: 0.2rem;
70 | font-size: 2rem;
71 | cursor: pointer;
72 | background-color: var(--lightest-green);
73 | padding: 5px;
74 | border-radius: 50%;
75 | color: var(--danger);
76 | font-weight: bold;
77 | }
78 |
79 | .btn-filter-float {
80 | display: none;
81 | }
82 |
83 | /* Responsiveness */
84 | @media only screen and (max-width: 900px) {
85 | .filters-sidebar {
86 | position: fixed;
87 | width: 15rem;
88 | height: fit-content;
89 | top: 0;
90 | left: 0;
91 | z-index: 3;
92 | box-shadow: var(--box-shadow-right);
93 | }
94 |
95 | .products-list {
96 | width: 100%;
97 | justify-content: center;
98 | }
99 |
100 | .container-main {
101 | display: block;
102 | position: relative;
103 | }
104 |
105 | .btn-filter-float {
106 | display: block;
107 | position: fixed;
108 | bottom: 1rem;
109 | right: 1.5rem;
110 | border: 2px solid var(--success);
111 | z-index: 3;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/backend/controllers/AuthController.js:
--------------------------------------------------------------------------------
1 | import { v4 as uuid } from "uuid";
2 | import { Response } from "miragejs";
3 | import { formatDate } from "../utils/authUtils";
4 | const sign = require("jwt-encode");
5 | /**
6 | * All the routes related to Auth are present here.
7 | * These are Publicly accessible routes.
8 | * */
9 |
10 | /**
11 | * This handler handles user signups.
12 | * send POST Request at /api/auth/signup
13 | * body contains {firstName, lastName, email, password}
14 | * */
15 |
16 | export const signupHandler = function (schema, request) {
17 | const { email, password, ...rest } = JSON.parse(request.requestBody);
18 | try {
19 | // check if email already exists
20 | const foundUser = schema.users.findBy({ email });
21 | if (foundUser) {
22 | return new Response(
23 | 422,
24 | {},
25 | {
26 | errors: ["Unprocessable Entity. Email Already Exists."],
27 | }
28 | );
29 | }
30 | const _id = uuid();
31 | const newUser = {
32 | _id,
33 | email,
34 | password,
35 | createdAt: formatDate(),
36 | updatedAt: formatDate(),
37 | ...rest,
38 | cart: [],
39 | wishlist: [],
40 | };
41 | const createdUser = schema.users.create(newUser);
42 | const encodedToken = sign({ _id, email }, process.env.REACT_APP_JWT_SECRET);
43 | return new Response(201, {}, { createdUser, encodedToken });
44 | } catch (error) {
45 | return new Response(
46 | 500,
47 | {},
48 | {
49 | error,
50 | }
51 | );
52 | }
53 | };
54 |
55 | /**
56 | * This handler handles user login.
57 | * send POST Request at /api/auth/login
58 | * body contains {email, password}
59 | * */
60 |
61 | export const loginHandler = function (schema, request) {
62 | const { email, password } = JSON.parse(request.requestBody);
63 | try {
64 | const foundUser = schema.users.findBy({ email });
65 | if (!foundUser) {
66 | return new Response(
67 | 404,
68 | {},
69 | { errors: ["The email you entered is not Registered. Not Found error"] }
70 | );
71 | }
72 | if (password === foundUser.password) {
73 | const encodedToken = sign(
74 | { _id: foundUser._id, email },
75 | process.env.REACT_APP_JWT_SECRET
76 | );
77 | foundUser.password = undefined;
78 | return new Response(200, {}, { foundUser, encodedToken });
79 | }
80 | new Response(
81 | 401,
82 | {},
83 | {
84 | errors: [
85 | "The credentials you entered are invalid. Unauthorized access error.",
86 | ],
87 | }
88 | );
89 | } catch (error) {
90 | return new Response(
91 | 500,
92 | {},
93 | {
94 | error,
95 | }
96 | );
97 | }
98 | };
99 |
--------------------------------------------------------------------------------
/src/components/AddressCard.jsx:
--------------------------------------------------------------------------------
1 | import "../styles/components/addressCard.css";
2 | import { useAddress } from "../context/address-context";
3 | import { toast } from "react-toastify";
4 | export const AddressCard = () => {
5 | const { addressState, dispatchAddress } = useAddress();
6 |
7 | return (
8 | <>
9 |
10 |
Address
11 |
Select Address for Delivery
12 | {addressState.addressList.map((address) => (
13 |
14 |
15 |
23 | dispatchAddress({
24 | type: "SET-ADDRESS-ID",
25 | payload: e.target.id,
26 | })
27 | }
28 | />
29 |
30 | {address.name}
31 | {
34 | dispatchAddress({
35 | type: "OPEN-EDIT-ADDRESS",
36 | payload: address,
37 | });
38 | }}
39 | >
40 | edit
41 |
42 | {
45 | dispatchAddress({
46 | type: "DELETE-ADDRESS",
47 | payload: address.id,
48 | });
49 | toast.success("Address Deleted");
50 | }}
51 | >
52 | delete
53 |
54 |
55 |
56 |
{address.flatName}
57 |
{address.area}
58 |
59 | {address.city}, {address.state}, {address.pincode}
60 |
61 |
{address.country}
62 |
Contact: {address.contact}
63 |
64 | ))}
65 |
68 | dispatchAddress({
69 | type: "TOGGLE-ADDRESS-MODAL",
70 | payload: "show-edit-box",
71 | })
72 | }
73 | >
74 | add Add New Address
75 |
76 |
77 | >
78 | );
79 | };
80 |
--------------------------------------------------------------------------------
/src/styles/pages/home.css:
--------------------------------------------------------------------------------
1 | .Home{
2 | font-family: var(--acme-font);
3 | background-color: var(--primary-transparent);
4 | min-height: 100vh;
5 | max-height: max-content;
6 | }
7 |
8 | /* Header */
9 | .hero-header{
10 | width: 100%;
11 | height: 80vh;
12 | color: var(--secondary);
13 | background-image: url(https://res.cloudinary.com/dvuh4fz9d/image/upload/v1648390942/farm-compressed_dqjqsw.webp);
14 | background-repeat:no-repeat;
15 | background-size:cover;
16 | background-position: center;
17 | position: relative;
18 | }
19 |
20 | .header-content{
21 | gap: 1.5rem;
22 | text-shadow: 1px 1px var(--white);
23 | }
24 |
25 | /* Btn Gradient Color*/
26 | .btn-grad {background-image: linear-gradient(to right, var(--secondary) 0%,var(--primary) 51%,var(--secondary) 100%)}
27 |
28 | .btn-shop-now{
29 | margin: 4rem;
30 | padding: 1rem 1.5rem;
31 | text-align: center;
32 | text-transform: uppercase;
33 | transition: 0.5s;
34 | background-size: 200% auto;
35 | color: var(--white);
36 | box-shadow: var(--button-shadow-1);
37 | border-radius: 10px;
38 | display: block;
39 | white-space: nowrap;
40 | border: 2px solid var(--primary);
41 | width: 10rem;
42 | font-size: 1.2rem;
43 | text-decoration: none;
44 | }
45 |
46 | .btn-shop-now:hover{
47 | border: 2px solid var(--white);
48 | background-position: right center; /* change the direction of the bg-color here */
49 |
50 | }
51 | .title-underline {
52 | background-color: var(--primary);
53 | width: 10rem;
54 | height: 4px;
55 | margin: 0.5rem auto 2rem;
56 | }
57 | .categories{
58 | display: grid;
59 | grid-template-columns: repeat(auto-fit, minmax(18rem,25rem));
60 | grid-gap: 0.7rem;
61 | grid-auto-rows: 14rem;
62 | justify-content: center;
63 | padding: 1rem
64 |
65 | }
66 | .category-items{
67 | position: relative;
68 | opacity: 0.8;
69 | border-radius: 10px;
70 |
71 | }
72 |
73 | .category-items:hover{
74 | opacity: 1;
75 | cursor: pointer;
76 | box-shadow: var(--box-shadow-2);
77 | }
78 |
79 | .categories img{
80 | height: 100%;
81 | width:100%;
82 | object-fit:cover;
83 | border-radius: 10px;
84 | }
85 |
86 | .category-item-text{
87 | position:absolute;
88 | top: 30%;
89 | left:20%;
90 | color: var(--primary);
91 | background-color: var(--white-gray);
92 | padding: 0.5rem;
93 | letter-spacing: 2px;
94 | }
95 |
96 | .banner-subtitle{
97 | color:var(--dark-blue);
98 | }
99 |
100 | @media screen and (max-width: 900px){
101 | .btn-shop-now{
102 | margin-top: 0;
103 | }
104 | .banner-title{
105 | font-size: 2rem;
106 | letter-spacing: 2px;
107 | margin: 0.5rem;
108 | line-height: normal;
109 |
110 | }
111 | .banner-subtitle{
112 | font-size: 1.5rem;
113 | letter-spacing: 1px;
114 | line-height: normal;
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/src/backend/controllers/WishlistController.js:
--------------------------------------------------------------------------------
1 | import { Response } from "miragejs";
2 | import { formatDate, requiresAuth } from "../utils/authUtils";
3 |
4 | /**
5 | * All the routes related to Wishlist are present here.
6 | * These are private routes.
7 | * Client needs to add "authorization" header with JWT token in it to access it.
8 | * */
9 |
10 | /**
11 | * This handler handles getting items to user's wishlist.
12 | * send GET Request at /api/user/wishlist
13 | * */
14 |
15 | export const getWishlistItemsHandler = function (schema, request) {
16 | const userId = requiresAuth.call(this, request);
17 | if (!userId) {
18 | new Response(
19 | 404,
20 | {},
21 | {
22 | errors: ["The email you entered is not Registered. Not Found error"],
23 | }
24 | );
25 | }
26 | const userWishlist = schema.users.findBy({ _id: userId }).wishlist;
27 | return new Response(200, {}, { wishlist: userWishlist });
28 | };
29 |
30 | /**
31 | * This handler handles adding items to user's wishlist.
32 | * send POST Request at /api/user/wishlist
33 | * body contains {product}
34 | * */
35 |
36 | export const addItemToWishlistHandler = function (schema, request) {
37 | const userId = requiresAuth.call(this, request);
38 | try {
39 | if (!userId) {
40 | new Response(
41 | 404,
42 | {},
43 | {
44 | errors: ["The email you entered is not Registered. Not Found error"],
45 | }
46 | );
47 | }
48 | const userWishlist = schema.users.findBy({ _id: userId }).wishlist;
49 | const { product } = JSON.parse(request.requestBody);
50 | userWishlist.push({
51 | ...product,
52 | createdAt: formatDate(),
53 | updatedAt: formatDate(),
54 | });
55 | this.db.users.update({ _id: userId }, { wishlist: userWishlist });
56 | return new Response(201, {}, { wishlist: userWishlist });
57 | } catch (error) {
58 | return new Response(
59 | 500,
60 | {},
61 | {
62 | error,
63 | }
64 | );
65 | }
66 | };
67 |
68 | /**
69 | * This handler handles removing items to user's wishlist.
70 | * send DELETE Request at /api/user/wishlist
71 | * body contains {product}
72 | * */
73 |
74 | export const removeItemFromWishlistHandler = function (schema, request) {
75 | const userId = requiresAuth.call(this, request);
76 | try {
77 | if (!userId) {
78 | new Response(
79 | 404,
80 | {},
81 | {
82 | errors: ["The email you entered is not Registered. Not Found error"],
83 | }
84 | );
85 | }
86 | let userWishlist = schema.users.findBy({ _id: userId }).wishlist;
87 | const productId = request.params.productId;
88 | userWishlist = userWishlist.filter((item) => item._id !== productId);
89 | this.db.users.update({ _id: userId }, { wishlist: userWishlist });
90 | return new Response(200, {}, { wishlist: userWishlist });
91 | } catch (error) {
92 | return new Response(
93 | 500,
94 | {},
95 | {
96 | error,
97 | }
98 | );
99 | }
100 | };
101 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | import { Server, Model, RestSerializer } from "miragejs";
2 | import {
3 | loginHandler,
4 | signupHandler,
5 | } from "./backend/controllers/AuthController";
6 | import {
7 | addItemToCartHandler,
8 | getCartItemsHandler,
9 | removeItemFromCartHandler,
10 | updateCartItemHandler,
11 | clearCartHandler,
12 | } from "./backend/controllers/CartController";
13 | import {
14 | getAllCategoriesHandler,
15 | getCategoryHandler,
16 | } from "./backend/controllers/CategoryController";
17 | import {
18 | getAllProductsHandler,
19 | getProductHandler,
20 | } from "./backend/controllers/ProductController";
21 | import {
22 | addItemToWishlistHandler,
23 | getWishlistItemsHandler,
24 | removeItemFromWishlistHandler,
25 | } from "./backend/controllers/WishlistController";
26 | import { categories } from "./backend/db/categories";
27 | import { products } from "./backend/db/products";
28 | import { users } from "./backend/db/users";
29 |
30 | export function makeServer({ environment = "development" } = {}) {
31 | return new Server({
32 | serializers: {
33 | application: RestSerializer,
34 | },
35 | environment,
36 | models: {
37 | product: Model,
38 | category: Model,
39 | user: Model,
40 | cart: Model,
41 | wishlist: Model,
42 | },
43 |
44 | // Runs on the start of the server
45 | seeds(server) {
46 | // disballing console logs from Mirage
47 | server.logging = false;
48 | products.forEach((item) => {
49 | server.create("product", { ...item });
50 | });
51 |
52 | users.forEach((item) =>
53 | server.create("user", { ...item, cart: [], wishlist: [] })
54 | );
55 |
56 | categories.forEach((item) => server.create("category", { ...item }));
57 | },
58 |
59 | routes() {
60 | this.namespace = "api";
61 | // auth routes (public)
62 | this.post("/auth/signup", signupHandler.bind(this));
63 | this.post("/auth/login", loginHandler.bind(this));
64 |
65 | // products routes (public)
66 | this.get("/products", getAllProductsHandler.bind(this));
67 | this.get("/products/:productId", getProductHandler.bind(this));
68 |
69 | // categories routes (public)
70 | this.get("/categories", getAllCategoriesHandler.bind(this));
71 | this.get("/categories/:categoryId", getCategoryHandler.bind(this));
72 |
73 | // cart routes (private)
74 | this.get("/user/cart", getCartItemsHandler.bind(this));
75 | this.post("/user/cart", addItemToCartHandler.bind(this));
76 | this.post("/user/cart/:productId", updateCartItemHandler.bind(this));
77 | this.delete(
78 | "/user/cart/:productId",
79 | removeItemFromCartHandler.bind(this)
80 | );
81 | this.delete("/user/clearCart", clearCartHandler.bind(this));
82 |
83 | // wishlist routes (private)
84 | this.get("/user/wishlist", getWishlistItemsHandler.bind(this));
85 | this.post("/user/wishlist", addItemToWishlistHandler.bind(this));
86 | this.delete(
87 | "/user/wishlist/:productId",
88 | removeItemFromWishlistHandler.bind(this)
89 | );
90 | },
91 | });
92 | }
93 |
--------------------------------------------------------------------------------
/src/components/HorizontalCard.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import { Link } from "react-router-dom";
3 | import { useCart } from "../context/cart-context";
4 | import { useWishList } from "../context/wishlist-context";
5 | import { removeItemFromCart } from "../services/cartServices";
6 | import { IsItemInWishList } from "../utils/isItemInWishList";
7 | import { useAuth } from "../context/auth-context";
8 | import {
9 | addItemToWishlist,
10 | removeItemFromWishlist,
11 | } from "../services/wishlistServices";
12 |
13 | export const HorizontalCard = ({ cardDetailsInCart }) => {
14 | const { _id, title, imgUrl, price, price_old, discount, quantity } =
15 | cardDetailsInCart;
16 |
17 | const { dispatchCart } = useCart();
18 | const { dispatchWishList } = useWishList();
19 | const { auth } = useAuth();
20 |
21 | const inWishList = IsItemInWishList(_id);
22 |
23 | function changeQuantity(quantityValue) {
24 | return dispatchCart({
25 | type: "TOGGLE-QUANTITY",
26 | payload: { _id, quantityValue },
27 | });
28 | }
29 |
30 | return (
31 | <>
32 | {/* */}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
{title}
42 |
43 |
44 |
45 |
46 | Quantity:
47 |
48 | changeQuantity(e.target.value)}
57 | />
58 |
59 |
60 | {inWishList ? (
61 |
64 | removeItemFromWishlist({
65 | auth,
66 | itemId: _id,
67 | dispatchWishList,
68 | })
69 | }
70 | >
71 | favorite
72 |
73 | ) : (
74 |
77 | addItemToWishlist({
78 | auth,
79 | itemDetails: cardDetailsInCart,
80 | dispatchWishList,
81 | })
82 | }
83 | >
84 | favorite_border
85 |
86 | )}
87 |
88 |
₹{price}
89 |
₹{price_old}
90 |
{discount}% off
91 |
92 |
96 | removeItemFromCart({ auth, itemId: _id, dispatchCart })
97 | }
98 | >
99 | remove_shopping_cart
100 | Remove
101 |
102 |
103 |
104 | >
105 | );
106 | };
107 |
--------------------------------------------------------------------------------
/src/components/ProductCard.js:
--------------------------------------------------------------------------------
1 | import { Link, useLocation, useNavigate } from "react-router-dom";
2 | import { IsItemInCart } from "../utils/isItemInCart";
3 | import { IsItemInWishList } from "../utils/isItemInWishList";
4 | import { useAuth } from "../context/auth-context";
5 | import { useCart } from "../context/cart-context";
6 | import { useWishList } from "../context/wishlist-context";
7 | import { addItemToCart } from "../services/cartServices";
8 | import {
9 | addItemToWishlist,
10 | removeItemFromWishlist,
11 | } from "../services/wishlistServices";
12 |
13 | export const ProductCard = ({ cardDetails }) => {
14 | const { dispatchCart } = useCart();
15 | const { dispatchWishList } = useWishList();
16 |
17 | const {
18 | _id,
19 | title,
20 | imgUrl,
21 | price,
22 | price_old,
23 | discount,
24 | rating,
25 | isBestSeller,
26 | isOutOfStock,
27 | } = cardDetails;
28 |
29 | const { auth } = useAuth();
30 | const inWishList = IsItemInWishList(_id);
31 | const inCart = IsItemInCart(_id);
32 | const navigate = useNavigate();
33 | const { pathname } = useLocation();
34 |
35 | return (
36 | <>
37 | {/* Card */}
38 |
39 | {isBestSeller &&
Best Seller }
40 | {isOutOfStock && (
41 |
44 | )}
45 |
46 | {inWishList ? (
47 |
50 | removeItemFromWishlist({
51 | auth,
52 | itemId: _id,
53 | dispatchWishList,
54 | })
55 | }
56 | >
57 | favorite
58 |
59 | ) : (
60 |
{
63 | auth.token
64 | ? addItemToWishlist({
65 | auth,
66 | itemDetails: cardDetails,
67 | dispatchWishList,
68 | })
69 | : navigate(
70 | "/login",
71 | { state: { from: { pathname: pathname } } },
72 | { replace: true }
73 | );
74 | }}
75 | >
76 | favorite_border
77 |
78 | )}
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
{title}
87 |
88 |
89 | {rating} star
90 |
91 |
92 |
₹{price}
93 |
₹{price_old}
94 |
{discount}% off
95 |
96 |
97 | {inCart ? (
98 |
99 |
shopping_cart
100 | Go to Cart
101 |
102 | ) : (
103 |
{
106 | auth.token
107 | ? addItemToCart({
108 | auth,
109 | itemDetails: { ...cardDetails, quantity: 1 },
110 | dispatchCart,
111 | })
112 | : navigate(
113 | "/login",
114 | { state: { from: { pathname: pathname } } },
115 | { replace: true }
116 | );
117 | }}
118 | >
119 | shopping_cart
120 | Add to Cart
121 |
122 | )}
123 |
124 | >
125 | );
126 | };
127 |
--------------------------------------------------------------------------------
/src/components/Navbar.js:
--------------------------------------------------------------------------------
1 | import "../styles/layouts/navbar.css";
2 | import { Link, useNavigate, useLocation } from "react-router-dom";
3 | import { useCart } from "../context/cart-context";
4 | import { useWishList } from "../context/wishlist-context";
5 | import { useAuth } from "../context/auth-context";
6 | import { toast } from "react-toastify";
7 | import { useAddress } from "../context/address-context";
8 | import { useState } from "react";
9 | import { MobileMenu } from "./MobileMenu";
10 | import { DebounceInput } from "react-debounce-input";
11 |
12 | export const Navbar = ({ setSearchQuery }) => {
13 | const { cartState, dispatchCart } = useCart();
14 | const { wishListState, dispatchWishList } = useWishList();
15 | const { auth, setAuth } = useAuth();
16 | const { dispatchAddress } = useAddress();
17 | const location = useLocation();
18 | const navigate = useNavigate();
19 | const [mobileMenuStatus, setMobileMenuStatus] = useState(false);
20 | const logoutHandler = () => {
21 | localStorage.removeItem("token");
22 | localStorage.removeItem("userData");
23 | setAuth({ ...auth, token: "", isLoggedIn: false });
24 | toast.success("Logout Success");
25 | navigate("/");
26 | dispatchCart({ type: "CLEAR-CART" });
27 | dispatchWishList({ type: "CLEAR-WISHLIST" });
28 | dispatchAddress({ type: "CLEAR-ADDRESS" });
29 | };
30 | return (
31 |
32 |
33 |
setMobileMenuStatus((toggler) => !toggler)}
36 | >
37 | menu
38 |
39 |
40 |
41 | AgroStores
42 |
43 |
44 |
45 |
46 |
47 | AgroStores
48 |
49 |
50 | {/* */}
51 |
52 |
53 | search
54 |
55 | navigate("/search")}
63 | onChange={(e) => setSearchQuery(e.target.value)}
64 | autoFocus={location.pathname === "/search"}
65 | />
66 |
67 |
68 | {/* */}
69 |
70 |
71 | {auth.token ? (
72 |
73 | Logout
74 |
75 | ) : (
76 |
77 | Login
78 |
79 | )}
80 |
81 |
82 |
83 | favorite
84 |
85 | {wishListState.wishlistItems.length}
86 |
87 |
88 |
89 |
90 |
91 |
92 | shopping_cart
93 |
94 | {cartState.cartItems.length}
95 |
96 |
97 |
98 |
99 |
100 | account_circle
101 |
102 |
103 |
104 |
105 |
112 |
113 | );
114 | };
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # 🌱 AgroStores 🛒
5 |
6 | ### An E-Commerce App for Buying Agro-Products.
7 |
8 |
9 |
10 | ---
11 |
12 | ## About AgroStores
13 |
14 | - AgroStores is a platform where one can easily buy Agricuture growth products online.
15 | - In this App, users can see products based on different categories such as Fertilizers, Pesticides, Farming Tools, Seeds, Safety Wearables, Electronic Devices, etc.
16 | - Additionally we have implemented various features such as add products to cart, add to wishlist, address management, order summary payment Integration, etc.
17 |
18 | ## Live Deployment
19 |
20 | Checkout the live deployment here: [AgroStores](https://agro-stores.netlify.app/)
21 |
22 | ---
23 |
24 | ## Features
25 |
26 | - [Home Page](https://agro-stores.netlify.app/) (public page), with navigation bar, header image, and various categories of products. User can easily view products based on selected category, add to cart, wishlist etc.
27 | - [Products Listing Page](https://agro-stores.netlify.app/products) (public page), with various categories which Filters Products based on Price Range of 200 to 2000 Rupees, Filter By Product Categories,Filter Products by Ratings, Sort Products by Price: Low to High & High to Low. User can also easily add products to both Wishlist and Cart by clicking buttons on each product card.
28 | - [Single Product Page](https://agro-stores.netlify.app/product/I5c1bFwd), (public page) - User can add product to cart, add to wishlist, view detailed product description along with Product Details.
29 | - Cart Page, (private page), consists of all products added in cart. User can increase/decrease the product quantity and see the cart price summary details and go to checkout page. Cart Page will only open if the user is logged In.
30 | - Checkout Page - User has to select the saved address or add/update address & check the price summary. User can then click on proceed to payment.
31 | - Wishlist Page (private page), consists of all products marked as favourite, user can also add products to cart and remove from wishlist.
32 | - [404 Page](https://agro-stores.netlify.app/dejdj) - Added 404 Page Not Found.
33 | - Profile Page (private page) - Profile Page shows name, email of logged in user and Quick Links for private pages, and saved addresses. User can also add new address, update & delete address.
34 | - Added Responsiveness for nearly all devices from 350px onwards.
35 | - Loaders & Alerts: Loaders are added when products are fetching, React Toastify is used for alerts message
36 | - Note:- 'Public Page'- Opens without Authentication, 'Private Page' - Opens only if user is Authenticated otherwise opens Login Page.
37 |
38 | ---
39 |
40 | ## Tech Stack and Tools
41 |
42 | - [Forest UI](https://forest-ui.netlify.app) for CSS and components.
43 | - React JS
44 | - React Router v6
45 | - React Context API + useReducer
46 | - Backend setup using [MockBee](https://mockbee.netlify.app/)
47 | - Used Async Await and Axios for API Request Calls.
48 | - Netlify for Deployment
49 | - Cloudinary for Hosting of Images
50 | - [Flaticons](https://www.flaticon.com/) for colorful icons.
51 | - React Toastify for alerts.
52 | - Razorpay Test Payments Integration.
53 |
54 | ---
55 |
56 | ## Issues & Suggestions
57 |
58 | - If you face any issues while using the app, then you can surely raise issue on this repo.
59 | - You can also give your valuable suggestions/ feedback to improve this project via Email (Mail-id in below section).
60 |
61 | ## Connect with me:
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/src/components/CartPriceCard.js:
--------------------------------------------------------------------------------
1 | import { useCart } from "../context/cart-context";
2 | import { Link, useLocation, useNavigate } from "react-router-dom";
3 | import { toast } from "react-toastify";
4 | import { useAddress } from "../context/address-context";
5 | import { clearCart } from "../services/cartServices";
6 | import { useAuth } from "../context/auth-context";
7 |
8 | export const CartPriceCard = () => {
9 | const { cartState } = useCart();
10 | const location = useLocation();
11 | const navigate = useNavigate();
12 | const { addressState } = useAddress();
13 | const { dispatchCart } = useCart();
14 | const { auth } = useAuth();
15 |
16 | function totalPrice() {
17 | let collectPrice = 0;
18 | cartState.cartItems.map(
19 | (item) => (collectPrice = collectPrice + item.price * item.quantity)
20 | );
21 | return collectPrice;
22 | }
23 | function totalOldPrice() {
24 | let collectOldPrice = 0;
25 | cartState.cartItems.map(
26 | (item) =>
27 | (collectOldPrice = collectOldPrice + item.price_old * item.quantity)
28 | );
29 | return collectOldPrice;
30 | }
31 |
32 | const deliveryCharge = () => (totalPrice() > 499 ? 0 : 100);
33 |
34 | const loadScript = () => {
35 | return new Promise((resolve) => {
36 | const script = document.createElement("script");
37 | script.src = "https://checkout.razorpay.com/v1/checkout.js";
38 | script.onload = () => {
39 | resolve(true);
40 | };
41 | script.onerror = () => {
42 | resolve(false);
43 | };
44 | document.body.appendChild(script);
45 | });
46 | };
47 |
48 | const handlePayment = async () => {
49 | if (addressState.addressList.length > 0 && addressState.addressSelectedId) {
50 | const res = await loadScript();
51 | if (!res) {
52 | toast.error("Razorpay SDK failed to load");
53 | }
54 |
55 | const options = {
56 | key: process.env.REACT_APP_RAZORPAY_KEY_ID,
57 | amount: (totalPrice() + deliveryCharge()) * 100,
58 | currency: "INR",
59 | name: "AgroStores",
60 | description: "Test Transaction",
61 | image: "",
62 | handler: async (response) => {
63 | clearCart({ auth, dispatchCart });
64 | toast.success("The payment was successfull");
65 | navigate(`/order-success/${response.razorpay_payment_id}`);
66 | },
67 | prefill: {
68 | name: "Kedar Kulkarni",
69 | email: "kedar@gmail.com",
70 | contact: "9999999998",
71 | },
72 | notes: {
73 | address: "AgroStores Corporate Office",
74 | },
75 | theme: {
76 | color: "#00d68b",
77 | },
78 | };
79 |
80 | const razorpayGateway = new window.Razorpay(options);
81 | razorpayGateway.open();
82 | } else {
83 | addressState.addressList.length == 0
84 | ? toast.error("Please Add address")
85 | : toast.error("Please Select Delivery Address");
86 | }
87 | };
88 |
89 | return (
90 | <>
91 |
92 |
Price Details
93 |
94 |
95 |
Price ({cartState.cartItems.length} Items)
96 |
₹{totalOldPrice()}
97 |
98 |
99 |
Discount
100 |
- ₹{totalOldPrice() - totalPrice()}
101 |
102 |
103 |
Delivery Charge
104 |
₹{deliveryCharge()}
105 |
106 |
107 |
108 |
Total Amount
109 |
₹{totalPrice() + deliveryCharge()}
110 |
111 |
112 |
113 |
114 | You will save ₹{totalOldPrice() - totalPrice()} on this order
115 |
116 |
117 | FREE Home Delivery on orders above ₹500
118 |
119 |
120 | {location.pathname == "/cart" ? (
121 |
125 | PROCEED TO CHECKOUT
126 |
127 | ) : (
128 |
132 | PROCEED TO PAYMENT
133 |
134 | )}
135 |
136 | >
137 | );
138 | };
139 |
--------------------------------------------------------------------------------
/src/pages/MyProfile.jsx:
--------------------------------------------------------------------------------
1 | import { NavLink } from "react-router-dom";
2 | import { Navbar } from "../components/Navbar";
3 | import { useAddress } from "../context/address-context";
4 | import { useState } from "react";
5 | import { toast } from "react-toastify";
6 |
7 | import "../styles/pages/profile.css";
8 | import { EditAddressModal } from "../components/EditAddressModal";
9 | export const MyProfile = () => {
10 | const userData = JSON.parse(localStorage.getItem("userData"));
11 | const { addressState, dispatchAddress } = useAddress();
12 | const [showAddressList, setShowAddressList] = useState(false);
13 | return (
14 | <>
15 |
16 |
17 |
18 |
19 |
20 |
21 |
My Profile
22 |
23 |
28 |
29 |
30 |
• About Me:
31 |
{`Name: ${userData.firstName} ${userData.lastName}`}
32 |
Email: {userData.email}
33 |
34 |
• Quick Links:
35 |
36 | My Cart
37 |
38 |
39 | My Wishlist
40 |
41 |
42 |
43 |
44 | • My Address List
45 | setShowAddressList((toggler) => !toggler)}
48 | >
49 | {showAddressList ? `close_fullscreen` : `open_in_full`}
50 |
51 |
52 | {showAddressList &&
53 | addressState.addressList.map((address, index) => (
54 |
58 |
59 |
63 | {index + 1}. {address.name}
64 | {
67 | dispatchAddress({
68 | type: "OPEN-EDIT-ADDRESS",
69 | payload: address,
70 | });
71 | }}
72 | >
73 | edit
74 |
75 | {
78 | dispatchAddress({
79 | type: "DELETE-ADDRESS",
80 | payload: address.id,
81 | });
82 | toast.success("Address Deleted");
83 | }}
84 | >
85 | delete
86 |
87 |
88 |
89 |
{address.flatName}
90 |
{address.area}
91 |
92 | {address.city}, {address.state}, {address.pincode}
93 |
94 |
{address.country}
95 |
Contact: {address.contact}
96 |
97 | ))}
98 | {showAddressList && (
99 |
102 | dispatchAddress({
103 | type: "TOGGLE-ADDRESS-MODAL",
104 | payload: "show-edit-box",
105 | })
106 | }
107 | >
108 | add Add New Address
109 |
110 | )}
111 |
112 |
113 |
114 |
115 |
116 |
117 | >
118 | );
119 | };
120 |
--------------------------------------------------------------------------------
/src/backend/controllers/CartController.js:
--------------------------------------------------------------------------------
1 | import { Response } from "miragejs";
2 | import { formatDate, requiresAuth } from "../utils/authUtils";
3 |
4 | /**
5 | * All the routes related to Cart are present here.
6 | * These are private routes.
7 | * Client needs to add "authorization" header with JWT token in it to access it.
8 | * */
9 |
10 | /**
11 | * This handler handles getting items to user's cart.
12 | * send GET Request at /api/user/cart
13 | * */
14 | export const getCartItemsHandler = function (schema, request) {
15 | const userId = requiresAuth.call(this, request);
16 | if (!userId) {
17 | new Response(
18 | 404,
19 | {},
20 | {
21 | errors: ["The email you entered is not Registered. Not Found error"],
22 | }
23 | );
24 | }
25 | const userCart = schema.users.findBy({ _id: userId }).cart;
26 | return new Response(200, {}, { cart: userCart });
27 | };
28 |
29 | /**
30 | * This handler handles adding items to user's cart.
31 | * send POST Request at /api/user/cart
32 | * body contains {product}
33 | * */
34 |
35 | export const addItemToCartHandler = function (schema, request) {
36 | const userId = requiresAuth.call(this, request);
37 | try {
38 | if (!userId) {
39 | new Response(
40 | 404,
41 | {},
42 | {
43 | errors: ["The email you entered is not Registered. Not Found error"],
44 | }
45 | );
46 | }
47 | const userCart = schema.users.findBy({ _id: userId }).cart;
48 | const { product } = JSON.parse(request.requestBody);
49 | userCart.push({
50 | ...product,
51 | createdAt: formatDate(),
52 | updatedAt: formatDate(),
53 | qty: 1,
54 | });
55 | this.db.users.update({ _id: userId }, { cart: userCart });
56 | return new Response(201, {}, { cart: userCart });
57 | } catch (error) {
58 | return new Response(
59 | 500,
60 | {},
61 | {
62 | error,
63 | }
64 | );
65 | }
66 | };
67 |
68 | /**
69 | * This handler handles removing items to user's cart.
70 | * send DELETE Request at /api/user/cart/:productId
71 | * */
72 |
73 | export const removeItemFromCartHandler = function (schema, request) {
74 | const userId = requiresAuth.call(this, request);
75 | try {
76 | if (!userId) {
77 | new Response(
78 | 404,
79 | {},
80 | {
81 | errors: ["The email you entered is not Registered. Not Found error"],
82 | }
83 | );
84 | }
85 | let userCart = schema.users.findBy({ _id: userId }).cart;
86 | const productId = request.params.productId;
87 | userCart = userCart.filter((item) => item._id !== productId);
88 | this.db.users.update({ _id: userId }, { cart: userCart });
89 | return new Response(200, {}, { cart: userCart });
90 | } catch (error) {
91 | return new Response(
92 | 500,
93 | {},
94 | {
95 | error,
96 | }
97 | );
98 | }
99 | };
100 |
101 | /**
102 | * This handler handles clearing all items to user's cart.
103 | * send DELETE Request at /api/user/clearCart
104 | * */
105 |
106 | export const clearCartHandler = function (schema, request) {
107 | const userId = requiresAuth.call(this, request);
108 | try {
109 | if (!userId) {
110 | new Response(
111 | 404,
112 | {},
113 | {
114 | errors: ["The email you entered is not Registered. Not Found error"],
115 | }
116 | );
117 | }
118 | let userCart = schema.users.findBy({ _id: userId }).cart;
119 | userCart = [];
120 | this.db.users.update({ _id: userId }, { cart: userCart });
121 | return new Response(200, {}, { cart: userCart });
122 | } catch (error) {
123 | return new Response(
124 | 500,
125 | {},
126 | {
127 | error,
128 | }
129 | );
130 | }
131 | };
132 | /**
133 | * This handler handles adding items to user's cart.
134 | * send POST Request at /api/user/cart/:productId
135 | * body contains {action} (whose 'type' can be increment or decrement)
136 | * */
137 |
138 | export const updateCartItemHandler = function (schema, request) {
139 | const productId = request.params.productId;
140 | const userId = requiresAuth.call(this, request);
141 | try {
142 | if (!userId) {
143 | new Response(
144 | 404,
145 | {},
146 | {
147 | errors: ["The email you entered is not Registered. Not Found error"],
148 | }
149 | );
150 | }
151 | const userCart = schema.users.findBy({ _id: userId }).cart;
152 | const { action } = JSON.parse(request.requestBody);
153 | if (action.type === "increment") {
154 | userCart.forEach((product) => {
155 | if (product._id === productId) {
156 | product.qty += 1;
157 | product.updatedAt = formatDate();
158 | }
159 | });
160 | } else if (action.type === "decrement") {
161 | userCart.forEach((product) => {
162 | if (product._id === productId) {
163 | product.qty -= 1;
164 | product.updatedAt = formatDate();
165 | }
166 | });
167 | }
168 | this.db.users.update({ _id: userId }, { cart: userCart });
169 | return new Response(200, {}, { cart: userCart });
170 | } catch (error) {
171 | return new Response(
172 | 500,
173 | {},
174 | {
175 | error,
176 | }
177 | );
178 | }
179 | };
180 |
--------------------------------------------------------------------------------
/src/pages/Login.js:
--------------------------------------------------------------------------------
1 | import { Navbar, Footer } from "../components/allComponents";
2 | import { Link, useNavigate, useLocation } from "react-router-dom";
3 | import "../styles/pages/login.css";
4 | import { useAuth } from "../context/auth-context";
5 | import { useState, useRef } from "react";
6 | import axios from "axios";
7 | import { toast } from "react-toastify";
8 | import { useCart } from "../context/cart-context";
9 | import { useWishList } from "../context/wishlist-context";
10 |
11 | export const Login = () => {
12 | const { auth, setAuth } = useAuth();
13 | const { dispatchCart } = useCart();
14 | const { dispatchWishList } = useWishList();
15 | const [passwordType, setPasswordType] = useState("password");
16 | const [testData, setTestData] = useState({ email: "", password: "" });
17 | const editLoginForm = useRef(null);
18 | const navigate = useNavigate();
19 | const location = useLocation();
20 | const from = location.state?.from?.pathname || "/";
21 |
22 | const handleLoginForm = (e) => {
23 | e.preventDefault();
24 | const form = editLoginForm.current;
25 | sendLoginRequest({
26 | email: form["emailId"].value,
27 | password: form["passwordField"].value,
28 | });
29 | editLoginForm.current.reset();
30 | };
31 |
32 | const sendLoginRequest = async (loginData) => {
33 | try {
34 | const response = await axios.post("/api/auth/login", loginData);
35 | localStorage.setItem("userData", JSON.stringify(response.data.foundUser));
36 | localStorage.setItem("token", response.data.encodedToken);
37 | setAuth({
38 | ...auth,
39 | token: response.data.encodedToken,
40 | isLoggedIn: true,
41 | });
42 |
43 | const prevCartData = response.data.foundUser.cart;
44 | const prevWishlistData = response.data.foundUser.wishlist;
45 |
46 | dispatchCart({ type: "UPDATE-CART", payload: prevCartData });
47 | dispatchWishList({ type: "UPDATE-WISHLIST", payload: prevWishlistData });
48 |
49 | setTestData({ email: "", password: "" });
50 | toast.success("Login Success");
51 |
52 | navigate(from, { replace: true });
53 | } catch (error) {
54 | console.log("Login Error", error);
55 | toast.error("Login Failed");
56 | }
57 | };
58 |
59 | return (
60 | <>
61 |
62 | {/* Main Container */}
63 |
141 |
142 | >
143 | );
144 | };
145 |
--------------------------------------------------------------------------------
/src/pages/SingleProductPage.jsx:
--------------------------------------------------------------------------------
1 | import { Navbar } from "../components/Navbar";
2 | import { Footer } from "../components/Footer";
3 | import "../styles/pages/singleProductPage.css";
4 | import { useParams, useNavigate, Link, useLocation } from "react-router-dom";
5 | import { Loader } from "../components/Loader";
6 | import { GetProductById } from "../services/getProductById";
7 | import { IsItemInCart } from "../utils/isItemInCart";
8 | import { IsItemInWishList } from "../utils/isItemInWishList";
9 | import { addItemToCart } from "../services/cartServices";
10 |
11 | import {
12 | addItemToWishlist,
13 | removeItemFromWishlist,
14 | } from "../services/wishlistServices";
15 | import { useAuth } from "../context/auth-context";
16 | import { useWishList } from "../context/wishlist-context";
17 | import { useCart } from "../context/cart-context";
18 |
19 | export const SingleProductPage = () => {
20 | const navigate = useNavigate();
21 | const { productId } = useParams();
22 | const { loader, product } = GetProductById(productId);
23 | const { auth } = useAuth();
24 | const { dispatchWishList } = useWishList();
25 | const { dispatchCart } = useCart();
26 | const inWishList = IsItemInWishList(productId);
27 | const inCart = IsItemInCart(productId);
28 | const { pathname } = useLocation();
29 | const {
30 | _id,
31 | title,
32 | imgUrl,
33 | price,
34 | price_old,
35 | discount,
36 | rating,
37 | categoryName,
38 | productDetails,
39 | description,
40 | } = product;
41 |
42 | return (
43 |
44 |
45 | {loader &&
}
46 | {!loader && (
47 | <>
48 |
49 |
50 |
51 |
{title}
52 |
53 | {rating} star
54 |
55 |
56 | Category: {categoryName}
57 |
58 |
59 |
₹{price}
60 |
₹{price_old}
61 |
{discount}% off
62 |
63 |
64 | {inCart ? (
65 |
69 | shopping_cart
70 | Go to Cart
71 |
72 | ) : (
73 | {
76 | auth.token
77 | ? addItemToCart({
78 | auth,
79 | itemDetails: { ...product, quantity: 1 },
80 | dispatchCart,
81 | })
82 | : navigate(
83 | "/login",
84 | { state: { from: { pathname: pathname } } },
85 | { replace: true }
86 | );
87 | }}
88 | >
89 | shopping_cart
90 | Add to Cart
91 |
92 | )}
93 | {inWishList ? (
94 |
97 | removeItemFromWishlist({
98 | auth,
99 | itemId: _id,
100 | dispatchWishList,
101 | })
102 | }
103 | >
104 | favorite
105 | Item in Wishlist
106 |
107 | ) : (
108 | {
111 | auth.token
112 | ? addItemToWishlist({
113 | auth,
114 | itemDetails: product,
115 | dispatchWishList,
116 | })
117 | : navigate(
118 | "/login",
119 | { state: { from: { pathname: pathname } } },
120 | { replace: true }
121 | );
122 | }}
123 | >
124 | favorite_border
125 | Add to Wishlist
126 |
127 | )}
128 |
129 |
130 |
131 |
132 |
133 |
Product Description
134 | {description}
135 |
136 |
137 |
Product Details
138 | {productDetails &&
139 | Object.entries(productDetails).map(([key, value]) => (
140 |
141 | {key}: {value}
142 |
143 | ))}
144 |
145 |
146 |
147 | >
148 | )}
149 |
150 | );
151 | };
152 |
--------------------------------------------------------------------------------
/src/components/FiltersBar.js:
--------------------------------------------------------------------------------
1 | import { toast } from "react-toastify";
2 | import { useProduct } from "../context/product-context";
3 | import { GetFeaturedCategories } from "../services/getFeaturedCategories";
4 |
5 | export const FiltersBar = () => {
6 | const { filterState, dispatchFilters } = useProduct();
7 | const ratingsInput = [
8 | {
9 | title: "4-star",
10 | label: "4 Stars & above",
11 | star: 4,
12 | id: "4S",
13 | },
14 | {
15 | title: "3-star",
16 | label: "3 Stars & above",
17 | star: 3,
18 | id: "3S",
19 | },
20 | {
21 | title: "2-star",
22 | label: "2 Stars & above",
23 | star: 2,
24 | id: "2S",
25 | },
26 | {
27 | title: "1-star",
28 | label: "1 Stars & above",
29 | star: 1,
30 | id: "1S",
31 | },
32 | ];
33 |
34 | return (
35 | <>
36 |
37 |
38 |
Filters
39 |
{
42 | dispatchFilters({ type: "CLEAR-FILTERS" });
43 | toast.success("Filter Cleared!");
44 | }}
45 | >
46 | CLEAR
47 |
48 |
49 |
52 |
53 |
54 | 200
55 | 1000
56 | 2000
57 |
58 | {
67 | dispatchFilters({
68 | type: "PRICE-RANGE-FILTER",
69 | payload: e.target.value,
70 | });
71 | }}
72 | />
73 |
74 | {/* Categories Filter */}
75 |
78 |
79 | {GetFeaturedCategories().map((item) => (
80 |
81 |
85 |
93 | dispatchFilters({
94 | type: "CATEGORIES",
95 | payload: e.target.name,
96 | })
97 | }
98 | />
99 |
100 | {item.categoryName}
101 |
102 |
103 | ))}
104 |
105 |
106 | {/* Ratings */}
107 |
110 |
111 | {ratingsInput.map((item) => (
112 |
113 |
117 |
123 | dispatchFilters({ type: "RATINGS", payload: item.star })
124 | }
125 | />
126 |
127 | {item.label}
128 |
129 |
130 | ))}
131 |
132 | {/* Sort-By */}
133 |
136 |
172 | {/* Hide Out of Stock */}
173 |
174 |
Product Availability
175 |
176 |
177 |
178 |
182 |
188 | dispatchFilters({ type: "REMOVE-OUT-OF-STOCK" })
189 | }
190 | />
191 | Hide Out of Stock
192 |
193 |
194 |
195 |
196 | >
197 | );
198 | };
199 |
--------------------------------------------------------------------------------
/src/components/EditAddressModal.jsx:
--------------------------------------------------------------------------------
1 | import { useAddress } from "../context/address-context";
2 | import { useRef } from "react";
3 | import "../styles/components/editAddressModal.css";
4 | import { v4 as uuid } from "uuid";
5 | import { toast } from "react-toastify";
6 |
7 | export const EditAddressModal = () => {
8 | const { addressState, dispatchAddress } = useAddress();
9 | const editAddressModal = useRef(null);
10 |
11 | if (addressState.isEditing) {
12 | const form = editAddressModal.current;
13 | const {
14 | name,
15 | flatName,
16 | area,
17 | landmark,
18 | city,
19 | pincode,
20 | state,
21 | country,
22 | contact,
23 | } = addressState.editData;
24 | form["fullName"].value = name;
25 | form["flatName"].value = flatName;
26 | form["area"].value = area;
27 | form["landmark"].value = landmark;
28 | form["city"].value = city;
29 | form["pincode"].value = pincode;
30 | form["stateSelect"].value = state;
31 | form["countrySelect"].value = country;
32 | form["contact"].value = contact;
33 | }
34 |
35 | const handleModalOnSubmit = (e) => {
36 | e.preventDefault();
37 | const form = editAddressModal.current;
38 |
39 | const addressData = {
40 | id: addressState.isEditing ? addressState.editData.id : uuid(),
41 | name: form["fullName"].value,
42 | flatName: form["flatName"].value,
43 | area: form["area"].value,
44 | landmark: form["landmark"].value,
45 | city: form["city"].value,
46 | pincode: form["pincode"].value,
47 | state: form["stateSelect"].value,
48 | country: form["countrySelect"].value,
49 | contact: form["contact"].value,
50 | };
51 |
52 | if (addressState.isEditing) {
53 | dispatchAddress({ type: "UPDATE-EDIT-ADDRESS", payload: addressData });
54 | toast.success("Address Updated!");
55 | } else {
56 | dispatchAddress({ type: "ADD-NEW-ADDRESS", payload: addressData });
57 | toast.success("Added New Address");
58 | }
59 | editAddressModal.current.reset();
60 | };
61 | const fillDummyAddressForm = (e) => {
62 | e.preventDefault();
63 | const form = editAddressModal.current;
64 | form["fullName"].value = "Ramesh Sharma";
65 | form["flatName"].value = "A/201, Govardhan Complex";
66 | form["area"].value = "Azad Nagar, Andheri-West";
67 | form["landmark"].value = "Near Azad Nagar Metro Station";
68 | form["city"].value = "Mumbai";
69 | form["pincode"].value = "400063";
70 | form["stateSelect"].value = "Maharashtra";
71 | form["contact"].value = "8899887788";
72 | };
73 | return (
74 |
190 | );
191 | };
192 |
--------------------------------------------------------------------------------
/src/pages/Signup.js:
--------------------------------------------------------------------------------
1 | import { Navbar, Footer } from "../components/allComponents";
2 | import { Link } from "react-router-dom";
3 | import "../styles/pages/signup.css";
4 | import { toast } from "react-toastify";
5 | import axios from "axios";
6 | import { useNavigate } from "react-router-dom";
7 | import { useAuth } from "../context/auth-context";
8 | import { useState, useRef } from "react";
9 |
10 | export const Signup = () => {
11 | const { auth, setAuth } = useAuth();
12 | const navigate = useNavigate();
13 | // Used to Show/Hide Passwords
14 | const [passwordType, setPasswordType] = useState({
15 | password: "password",
16 | confirmPassword: "password",
17 | });
18 |
19 | const editSignupForm = useRef(null);
20 | const handleSignupForm = (e) => {
21 | e.preventDefault();
22 | const form = editSignupForm.current;
23 | if (form["passwordField"].value === form["confirmPasswordField"].value) {
24 | signUpHandler({
25 | firstName: form["firstName"].value,
26 | lastName: form["lastName"].value,
27 | email: form["emailId"].value,
28 | password: form["passwordField"].value,
29 | });
30 | editSignupForm.current.reset();
31 | } else {
32 | toast.error("Passwords Do Not Match!!");
33 | }
34 | };
35 | const signUpHandler = async (signUpData) => {
36 | try {
37 | const response = await axios.post(`/api/auth/signup`, signUpData);
38 | // saving the encodedToken in the localStorage
39 | localStorage.setItem("token", response.data.encodedToken);
40 | localStorage.setItem(
41 | "userData",
42 | JSON.stringify(response.data.createdUser)
43 | );
44 | setAuth({
45 | ...auth,
46 | token: response.data.encodedToken,
47 | isLoggedIn: true,
48 | });
49 | toast.success("Sign up Successful!!");
50 | navigate("/products");
51 | } catch (error) {
52 | console.log("Signup Error", error);
53 | toast.error("Server Error, Unable to Signup", error);
54 | }
55 | };
56 |
57 | const fillDummyData = (e) => {
58 | e.preventDefault();
59 | const form = editSignupForm.current;
60 | form["firstName"].value = "Ram";
61 | form["lastName"].value = "Mishra";
62 | form["emailId"].value = "ram.mishra@gmail.com";
63 | form["passwordField"].value = "ramMishra@123";
64 | form["confirmPasswordField"].value = "ramMishra@123";
65 | };
66 |
67 | return (
68 | <>
69 |
70 | {/* Main Container */}
71 |
210 |
211 | >
212 | );
213 | };
214 |
--------------------------------------------------------------------------------
/src/backend/db/products.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Product Database can be added here.
3 | * You can add products of your wish with different attributes
4 | * */
5 |
6 | export const products = [
7 | {
8 | _id: "I5c1bFwd",
9 | title: "TrustBasket Organic Vermicompost Fertilizer Manure for Plants(5KG)",
10 | imgUrl: "https://m.media-amazon.com/images/I/71iPA5LfB7L._AC_UL320_.jpg",
11 | price: 332,
12 | price_old: 349,
13 | discount: 5,
14 | rating: 4.5,
15 | isBestSeller: true,
16 | isOutOfStock: false,
17 | categoryName: "Fertilizers",
18 | productDetails: {
19 | Brand: "TrustBasket",
20 | Weight: "5000 Grams",
21 | Form: "Powder",
22 | Volume: "5 Litres",
23 | },
24 | description: `1. 100% organic
25 | 2. Improves soil aeration
26 | 3. Enriches soil with micro-organisms (adding enzymes such as phosphate and cellulose)
27 | 4. Microbial activity in worm castings is 10 to 20 times higher than in the soil and organic matter that the worm ingests
28 | 5. Attracts deep-burrowing earthworms already present in the soil.
29 | 6. Increases the immune system of plants.
30 | 7. Easy to store and easy to use.`,
31 | },
32 | {
33 | _id: "VhibWhCO",
34 | title:
35 | "TrustBasket Enriched Organic Earth Magic Potting Soil Fertilizer for Plants(5KG)",
36 | imgUrl: "https://m.media-amazon.com/images/I/61fUoGkNdHL._AC_UL320_.jpg",
37 | price: 950,
38 | price_old: 1999,
39 | discount: 52,
40 | rating: 3.3,
41 | isBestSeller: true,
42 | isOutOfStock: false,
43 | categoryName: "Fertilizers",
44 | productDetails: {
45 | Brand: "TrustBasket",
46 | Weight: "5000 Grams",
47 | Form: "Granules",
48 | Volume: "5 Litres",
49 | },
50 | description: `Contains microbes which enhance the soil properties
51 | Completely organic and does not contain any harmful chemicals
52 | Contains micro and macro nutrients. Has good water holding capacity
53 | Its antifungal property helps the plants to grow healthy`,
54 | },
55 | {
56 | _id: "w1zV3ayR",
57 | title:
58 | "Fieldstar 8-Litre Plastic Manual Sprayer for spraying fertilizers in Farms.",
59 | imgUrl: "https://m.media-amazon.com/images/I/61xnYxYWFsL._AC_UL320_.jpg",
60 | price: 950,
61 | price_old: 1999,
62 | discount: 52,
63 | rating: 4.8,
64 | isBestSeller: false,
65 | isOutOfStock: false,
66 | categoryName: "Farming Tools",
67 | productDetails: {
68 | Brand: "FIELDSTAR",
69 | Material: "Others",
70 | Color: "Green",
71 | },
72 | description: `Used for spraying water and pesticides
73 | Package Contents: 1 Sprayer
74 | 8 LITRE TANK
75 | BEST QUALITY`,
76 | },
77 | {
78 | _id: "sP6NCOgs",
79 | title:
80 | "FreshDcart Solar Soil Plant Care New 3 In 1 Water Moisture Soil Sensor.",
81 | imgUrl: "https://m.media-amazon.com/images/I/71WLLgqaUCL._AC_UL320_.jpg",
82 | price: 589,
83 | price_old: 1010,
84 | discount: 63,
85 | rating: 3.8,
86 | isBestSeller: false,
87 | isOutOfStock: false,
88 | categoryName: "Electronic Devices",
89 | productDetails: {
90 | Brand: "FreshDcart",
91 | Material: "Others",
92 | DeviceType: "Electronic",
93 | },
94 | description: `Plant Care New 3 in 1 Hydroponic Plants Soil Moisture PH Light Meter good for agriculture soil testing use
95 | Scientifically Accurate: Easy to read water moisture, ph and light levels, promotes healthy plants`,
96 | },
97 | {
98 | _id: "wxOaYSr5",
99 | title: "WOLFGARTEN Steel Multi Star Culti-Weeder without Handle for Farms",
100 | imgUrl:
101 | "https://images-eu.ssl-images-amazon.com/images/I/61f0YRC22oL._AC_UL160_SR160,160_.jpg",
102 | price: 1270,
103 | price_old: 2000,
104 | discount: 37,
105 | rating: 3.5,
106 | isBestSeller: false,
107 | isOutOfStock: false,
108 | categoryName: "Farming Tools",
109 | productDetails: {
110 | brand: "WOLFGARTEN",
111 | material: "Steel",
112 | Weight: "520 grams",
113 | },
114 | description: `Made In Germany
115 | Optional handle choice- 140cm, 150cm, 170cm (To be Purchased Saperatly of your choice)
116 | For hoeing, weeding and trenching
117 | Ideal for working around broad leaved shrubs and plants
118 | Part of the multi-change click system-any handle fits 50 tool heads`,
119 | },
120 | {
121 | _id: "ZVZzMdoL",
122 | title:
123 | "FULLFILLBUY Farmer Safety Shoes, Paddy shoes, Agriculture Safety Shoes.",
124 | imgUrl: "https://m.media-amazon.com/images/I/31TYpYBcsoL._AC_UL320_.jpg",
125 | price: 1499,
126 | price_old: 2000,
127 | discount: 25,
128 | rating: 2.5,
129 | isBestSeller: false,
130 | isOutOfStock: false,
131 | categoryName: "Safety Wearables",
132 | productDetails: {
133 | brand: "FULLFILLBUY",
134 | material: "PVC material",
135 | Weight: "520 grams",
136 | dimension: "30 x 10 x 25 cm",
137 | },
138 | description: `Sole: Polyvinyl Chloride
139 | Closure: Pull On
140 | Shoe Width: Wide
141 | High quality new PVC material, No recycled material
142 | Softness, flexibility, durable and unbreakable
143 | Seamless forming, military quality, long wear and no leakage
144 | Wear-resisting outsole, anti-slip and shock absorption
145 | High quality and easy to clean`,
146 | },
147 | {
148 | _id: "2nQRAjxP",
149 | title:
150 | "Pick Ur Needs® Prime Metal 100w Rechargeable Waterproof Led Torch Light.",
151 | imgUrl: "https://m.media-amazon.com/images/I/61sXMk18mRL._AC_UL320_.jpg",
152 | price: 1799,
153 | price_old: 2695,
154 | discount: 33,
155 | rating: 4.0,
156 | isBestSeller: false,
157 | isOutOfStock: false,
158 | categoryName: "Electronic Devices",
159 | productDetails: {
160 | brand: "Pick Ur Needs®",
161 | voltage: "120 Volts",
162 | LuminousFlux: "7000 Lumen",
163 | Battery: "Lithium_ion",
164 | },
165 | description: `Rechargeable LED Flashlight Spotlight Waterproof Handheld Searchlight
166 | Easy to hold
167 | In your home, outside, in the car… it’s THE flashlight for almost any situation.
168 | Thanks to its lightweight and compact size,
169 | the Pick Ur Needs Rechargeable Spotlight is designed for a wide variety of heavy-duty uses.
170 | Slips easily into backpacks for hunting, fishing, camping, spelunking, hiking, or walking.
171 | Can also be stored on a boat, in a camper, or in your automobile`,
172 | },
173 | {
174 | _id: "VOJG0b5b",
175 | title:
176 | "3M Polycarbonate Safety Goggles for Chemical Splash,Pack of 1,Clear",
177 | imgUrl: "https://m.media-amazon.com/images/I/51tkdrj3cAL._AC_UL320_.jpg",
178 | price: 110,
179 | price_old: 115,
180 | discount: 4,
181 | rating: 2.1,
182 | isBestSeller: false,
183 | isOutOfStock: true,
184 | categoryName: "Safety Wearables",
185 | productDetails: {
186 | color: "transparent",
187 | Weight: "100 grams",
188 | Material: "Polycarbonate",
189 | BrandName: "3M",
190 | },
191 | description: `Protects from chemical splashes
192 | Polycarbonate frame or lens
193 | Wear over prescription lenses, meets ANSI Z87.1-2003
194 | 4 one-way vents allow ventilation and does not allow splashes to enter
195 | colour: clear`,
196 | },
197 | {
198 | _id: "dF0FQvJL",
199 | title:
200 | "Bayer CropScience Confidor Insecticide for Aphids, Whitefly,Control (50ML)",
201 | imgUrl: "https://m.media-amazon.com/images/I/41or-q4ie5L._AC_UY218_.jpg",
202 | price: 194,
203 | price_old: 230,
204 | discount: 16,
205 | rating: 4.2,
206 | isBestSeller: false,
207 | isOutOfStock: true,
208 | categoryName: "Pesticides",
209 | productDetails: {
210 | brand: "Bayer CropScience Limited",
211 | weight: "120 g",
212 | Dimensions: "45 x 5 x 149 Millimeters",
213 | },
214 | description: `Imidacloprid belongs to chemical class of neonicotinoid insecticides.
215 | It exhibits excellent systemic properties and a significant residual activity.;
216 | Imidacloprid has a broad spectrum of activity, particularly against suckinginsects,
217 | various species of beetles, some species of flies, leaf miners and termites;
218 | Its outstanding biological efficacy, especially its excellent root-systemicproperties,
219 | its broad spectrum of activity, good long lasting effect - combinedwith low application rates
220 | and good plant compatibility, has made the product the first choice of the farmer;`,
221 | },
222 | {
223 | _id: "l07mWMm3",
224 | title:
225 | "Katra Fertilizers Lysorus (1 g+50 ml) Anti-Virus and Bacteria Virucide for Virus Control",
226 | imgUrl: "https://m.media-amazon.com/images/I/81ZCu2bU-+L._AC_UY218_.jpg",
227 | price: 499,
228 | price_old: 650,
229 | discount: 23,
230 | rating: 2.2,
231 | isBestSeller: false,
232 | isOutOfStock: false,
233 | categoryName: "Fertilizers",
234 | productDetails: {
235 | brand: "Katra Fertilizers",
236 | Item_Form: "Powder",
237 | Liquid_Volume: "0.05 Litres",
238 | },
239 | description: `Lysorus (Anti-virus & bacteria) is an multi purpose enzyme which protect plants from bacteria and virus, it is capable of breaking the chemical bonds of the outer cell wall of the bacteria and virus.
240 | Lysorus has true activity against bacteria and virus leading to long term protection of crop from bacterial and viral infection.
241 | Lysorus is 100% toxic free product. Its uses is environmentally safe.
242 | Doses : 1gm lysorus powder + 50 ml activator / acer Method of application :
243 | First dissolve 1gm lysorus powder then mix 50 ml Activator into 150 ltr water then spray it on crop of 1 acre.
244 | Doses: 1 Pack is Sufficient for 10-time use in 1 Ltr water. Method of Application:-Mix .1gm Powder with 5ml activator(which is already given in the pack) in 1 Ltr. water and spray freely on the plant, Shake well before use.`,
245 | },
246 | {
247 | _id: "hKRD5d0S",
248 | title:
249 | "GREEN WORLD Hybrid Bajra/Pearl Millet Seeds For Farming Or Agriculture (1/2 Kg Seeds)",
250 | imgUrl: "https://m.media-amazon.com/images/I/51nvZ2K9eEL._AC_UL320_.jpg",
251 | price: 199,
252 | price_old: 299,
253 | discount: 33,
254 | rating: 3.2,
255 | isBestSeller: false,
256 | isOutOfStock: false,
257 | categoryName: "Seeds",
258 | productDetails: {
259 | brand: "Green World",
260 | Colour: "Green",
261 | Item_Weight: "500 Grams",
262 | Special_Feature: "Drought Tolerant",
263 | },
264 | description: `GREEN WORLD Hybrid Bajra/Pearl Millet Seeds For Farming Or Agriculture (1/2 Kg Seeds)`,
265 | },
266 | {
267 | _id: "s3QBAf2P",
268 | title: "ONLY FOR ORGANIC UV Stabilized Plastic Shade Net, 3x10m (Green)",
269 | imgUrl: "https://m.media-amazon.com/images/I/91vmzczsk3L._AC_UL320_.jpg",
270 | price: 1199,
271 | price_old: 2100,
272 | discount: 43,
273 | rating: 4.2,
274 | isBestSeller: false,
275 | isOutOfStock: false,
276 | categoryName: "Farming Tools",
277 | productDetails: {
278 | brand: "ONLY FOR ORGANIC",
279 | Weight: "420 g",
280 | dimensions: "28.5 x 19.4 x 8.3 cm; 420 Grams",
281 | },
282 | description: `UV Stabilized Agro-Shade Nets - 50% Dimension :Width : 3 Meter Length : As per Your requirement (Upto 50 meter per roll). This nets are U.V. stabilized with higher degree of stabilization to prevent nets from degradation by Ultra violet ray of sun and thus they last longer than others. · It helps in controlling temperature. light, water and moisture of nature as per requirement resulting in best output and quality of crop ·
283 | Green Shade Net protects the crop against the infrared (I.R.) and Ultraviolet Rays (U.V.).
284 | Protects the crop against the heavy rain and air ·
285 | Green house enables the farmer to best utilize the waste land and increase the cultivation area for increasing the output · With green net even small piece of land can yield in high output ·
286 | The level of Carbon dioxide remains high in green house which increases the photosynthesis process and results in higher yield of the crop cultivated · With green house the environment of the crop can be controlled resulting in healthy crop with better quality and better yield
287 | `,
288 | },
289 | ];
290 |
--------------------------------------------------------------------------------