├── .vscode
└── settings.json
├── public
├── robots.txt
├── favicon.ico
├── logo192.png
├── logo512.png
├── manifest.json
└── index.html
├── src
├── componets
│ ├── TotalPrice.jsx
│ ├── CardProducts.jsx
│ └── Navbar.js
├── setupTests.js
├── App.test.js
├── pages
│ ├── Home.js
│ ├── Product
│ │ ├── actions
│ │ │ └── productAction.js
│ │ ├── container
│ │ │ └── productContainer.js
│ │ ├── reducer
│ │ │ └── productReducer.js
│ │ └── pages
│ │ │ └── Product.js
│ └── Cart
│ │ ├── container
│ │ └── cartContainer.js
│ │ ├── actions
│ │ └── cartAction.js
│ │ ├── reducer
│ │ └── cartReducer.js
│ │ └── pages
│ │ └── Cart.js
├── redux
│ ├── reducers
│ │ └── index.js
│ ├── constants
│ │ └── action-type.js
│ └── store.js
├── reportWebVitals.js
├── apis
│ └── callApi.js
├── App.js
├── App.css
├── index.js
├── genai
│ └── conversation.py
├── logo.svg
└── index.css
├── .gitignore
├── package.json
└── README.md
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.inlineSuggest.showToolbar": "onHover"
3 | }
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/khushi2706/E-commerce-website-using-React-Redux/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/khushi2706/E-commerce-website-using-React-Redux/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/khushi2706/E-commerce-website-using-React-Redux/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/src/componets/TotalPrice.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const TotalPrice = () => {
4 | return (
5 |
TotalPrice
6 | )
7 | }
8 |
9 | export default TotalPrice
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import Product from "./Product/container/productContainer";
3 |
4 | export default class Home extends Component {
5 | render() {
6 | return (
7 | <>
8 |
11 | >
12 | );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/redux/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import { productReducer } from "../../pages/Product/reducer/productReducer";
3 | import { cartReducer } from "../../pages/Cart/reducer/cartReducer";
4 |
5 | const reducers = combineReducers({
6 | productReducer,
7 | cartReducer,
8 | });
9 |
10 | export default reducers;
11 |
--------------------------------------------------------------------------------
/src/redux/constants/action-type.js:
--------------------------------------------------------------------------------
1 | export const ActionTypes = {
2 | FETCH_PRODUCTS: "FETCH_PRODUCTS",
3 | SELECTED_PRODUCT: "SELECTED_PRODUCT",
4 | REMOVE_SELECTED_PRODUCT: "REMOVE_SELECTED_PRODUCT",
5 | ADD_TO_CART: "ADD_TO_CART",
6 | REMOVE_FROM_CART: "REMOVE_FROM_CART",
7 | INCREASE_QUANTITY: "INCREASE_QUANTITY",
8 | DECREASE_QUANTITY: "DECREASE_QUANTITY",
9 | };
10 |
--------------------------------------------------------------------------------
/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { legacy_createStore as createStore, applyMiddleware } from "redux";
2 | import reducers from "./reducers/index";
3 | import thunk from "redux-thunk";
4 | import promise from "redux-promise-middleware";
5 |
6 | const store = createStore(
7 | reducers,
8 | {} /* preloadedState, */,
9 | applyMiddleware(thunk, promise)
10 | );
11 |
12 | export default store;
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.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/apis/callApi.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const BASE_URL = "https://fakestoreapi.com";
4 |
5 | export const callAPI = async (method, endpoint, data = null) => {
6 | const config = {
7 | url: `${BASE_URL}${endpoint}`,
8 | method,
9 | data,
10 | };
11 |
12 | return new Promise((resolve, reject) => {
13 | axios(config)
14 | .then((response) => {
15 | resolve(response.data);
16 | })
17 | .catch((err) => {
18 | reject(err.response);
19 | });
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/src/pages/Product/actions/productAction.js:
--------------------------------------------------------------------------------
1 | import { callAPI } from "../../../apis/callApi";
2 | import { ActionTypes } from "../../../redux/constants/action-type";
3 |
4 | export const fetchProductsAction = () => {
5 | const products = callAPI("GET", "/products");
6 | return {
7 | type: ActionTypes.FETCH_PRODUCTS,
8 | payload: products,
9 | };
10 | };
11 |
12 | export const selectedProductAction = (product) => {
13 | return {
14 | type: ActionTypes.SELECTED_PRODUCT,
15 | payload: product,
16 | };
17 | };
18 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import logo from "./logo.svg";
2 | import "./App.css";
3 | import { BrowserRouter, Routes, Route } from "react-router-dom";
4 | import Navbar from "./componets/Navbar";
5 | import Home from "./pages/Home";
6 | import Cart from "./pages/Cart/container/cartContainer";
7 | function App() {
8 | return (
9 | <>
10 |
11 |
12 |
13 | }>
14 | }>
15 |
16 |
17 | >
18 | );
19 | }
20 |
21 | export default App;
22 |
--------------------------------------------------------------------------------
/src/pages/Cart/container/cartContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from "react-redux";
2 | import Cart from "../pages/Cart";
3 | import { decreaseQuantityAction, increaseQuantityAction, removeFromCartAction } from "../actions/cartAction";
4 |
5 | const mapStateToProps = (state) => ({
6 | cartReducer: state.cartReducer,
7 | });
8 |
9 | const mapDispatchToProps = (dispatch) => ({
10 | removeFromCart: (productId) => dispatch(removeFromCartAction(productId)),
11 | increaseQuantity: (id) => dispatch(increaseQuantityAction(id)),
12 | decreaseQuantity: (id) => dispatch(decreaseQuantityAction(id)),
13 | });
14 |
15 | export default connect(mapStateToProps, mapDispatchToProps)(Cart);
16 |
--------------------------------------------------------------------------------
/src/pages/Product/container/productContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from "react-redux";
2 | import Product from "../pages/Product";
3 | import { fetchProductsAction } from "../actions/productAction";
4 | import { addToCartAction } from "../../Cart/actions/cartAction";
5 |
6 | const mapStateToProps = (state) => ({
7 | productReducer: state.productReducer,
8 | cartReducer: state.cartReducer,
9 | });
10 |
11 | const mapDispatchToProps = (dispatch) => ({
12 | fetchProducts: () => dispatch(fetchProductsAction()),
13 | addToCart: (productData) => dispatch(addToCartAction(productData)),
14 | });
15 |
16 | export default connect(mapStateToProps, mapDispatchToProps)(Product);
17 |
--------------------------------------------------------------------------------
/src/pages/Cart/actions/cartAction.js:
--------------------------------------------------------------------------------
1 | import { ActionTypes } from "../../../redux/constants/action-type";
2 |
3 | export const addToCartAction = (product = {}) => {
4 | return {
5 | type: ActionTypes.ADD_TO_CART,
6 | payload: { product },
7 | };
8 | };
9 |
10 | export const removeFromCartAction = (productId) => {
11 | return {
12 | type: ActionTypes.REMOVE_FROM_CART,
13 | payload: { id: productId },
14 | };
15 | };
16 |
17 | export const increaseQuantityAction = (id) => ({
18 | type: ActionTypes.INCREASE_QUANTITY,
19 | payload: { id },
20 | });
21 |
22 | export const decreaseQuantityAction = (id) => ({
23 | type: ActionTypes.DECREASE_QUANTITY,
24 | payload: { id },
25 | });
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import "./index.css";
4 | import App from "./App";
5 | import reportWebVitals from "./reportWebVitals";
6 | import { Provider } from "react-redux";
7 | import store from "./redux/store";
8 |
9 | const root = ReactDOM.createRoot(document.getElementById("root"));
10 | root.render(
11 |
12 |
13 |
14 |
15 |
16 | );
17 |
18 | // If you want to start measuring performance in your app, pass a function
19 | // to log results (for example: reportWebVitals(console.log))
20 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
21 | reportWebVitals();
22 |
--------------------------------------------------------------------------------
/src/pages/Product/reducer/productReducer.js:
--------------------------------------------------------------------------------
1 | import { ActionTypes } from "../../../redux/constants/action-type";
2 |
3 | const initialState = {
4 | products: [],
5 | status: "idle", // Added an initial status of "idle" for clarity
6 | loading: false, // Added a loading flag
7 | };
8 |
9 | export const productReducer = (state = initialState, { type, payload }) => {
10 | switch (type) {
11 | case `${ActionTypes.FETCH_PRODUCTS}_FULFILLED`:
12 | return { ...state, status: "success", loading: false, products: payload };
13 | case `${ActionTypes.FETCH_PRODUCTS}_PENDING`:
14 | return { ...state, status: "pending", loading: true };
15 | case `${ActionTypes.FETCH_PRODUCTS}_REJECTED`:
16 | return { ...state, status: "rejected", loading: false };
17 | // case ActionTypes.SELECTED_PRODUCT:
18 | // return state;
19 | // You can add more cases for other action types as needed
20 | default:
21 | return state;
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/src/componets/CardProducts.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const CartProductCard = ({ product, onRemove,increase,decrease }) => {
4 |
5 | return (
6 |
7 |

8 |
9 |
{product.title}
10 |
₹{product.price}
11 |
12 |
18 |
{product.quantity}
19 |
25 |
31 |
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default CartProductCard;
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-shop",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@redux-devtools/extension": "^3.2.5",
7 | "@reduxjs/toolkit": "^1.9.5",
8 | "@testing-library/jest-dom": "^5.17.0",
9 | "@testing-library/react": "^13.4.0",
10 | "@testing-library/user-event": "^13.5.0",
11 | "axios": "^1.4.0",
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0",
14 | "react-redux": "^8.1.1",
15 | "react-router-dom": "^6.14.2",
16 | "react-scripts": "5.0.1",
17 | "redux": "^4.2.1",
18 | "redux-promise-middleware": "^6.1.3",
19 | "redux-thunk": "^2.4.2",
20 | "web-vitals": "^2.1.4"
21 | },
22 | "scripts": {
23 | "start": "react-scripts start",
24 | "build": "react-scripts build",
25 | "test": "react-scripts test",
26 | "eject": "react-scripts eject"
27 | },
28 | "eslintConfig": {
29 | "extends": [
30 | "react-app",
31 | "react-app/jest"
32 | ]
33 | },
34 | "browserslist": {
35 | "production": [
36 | ">0.2%",
37 | "not dead",
38 | "not op_mini all"
39 | ],
40 | "development": [
41 | "last 1 chrome version",
42 | "last 1 firefox version",
43 | "last 1 safari version"
44 | ]
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/componets/Navbar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import { Link } from "react-router-dom";
4 |
5 | class Navbar extends Component {
6 | constructor() {
7 | super();
8 | this.state = {
9 | cartCount: 0,
10 | };
11 | }
12 |
13 | componentDidUpdate(prevProps) {
14 | const { cartReducer } = this.props;
15 | if (cartReducer.cart !== prevProps.cartReducer.cart) {
16 | this.setState({ cartCount: cartReducer.cart.length });
17 | }
18 | }
19 |
20 | render() {
21 | const { cartCount } = this.state;
22 |
23 | return (
24 |
31 |
32 | HappyKart 🛍️
33 |
34 |
35 |
36 | Home
37 |
38 |
39 | Cart
40 |
41 | Cart items: {cartCount}
42 |
43 |
44 | );
45 | }
46 | }
47 |
48 | const mapStateToProps = (state) => ({
49 | cartReducer: state.cartReducer,
50 | });
51 |
52 | export default connect(mapStateToProps)(Navbar);
53 |
--------------------------------------------------------------------------------
/src/pages/Cart/reducer/cartReducer.js:
--------------------------------------------------------------------------------
1 | import { ActionTypes } from "../../../redux/constants/action-type";
2 |
3 | const initialState = {
4 | cart: [], // each item: { id, name, price, quantity }
5 | totalPrice: 0,
6 | };
7 |
8 | const calculateTotal = (cart) => {
9 | return cart.reduce((total, item) => total + item.price * item.quantity, 0);
10 | };
11 |
12 | export const cartReducer = (state = initialState, { type, payload }) => {
13 | let updatedCart;
14 |
15 | switch (type) {
16 | case ActionTypes.ADD_TO_CART: {
17 | const existingProduct = state.cart.find(item => item.id === payload.product.id);
18 |
19 | if (existingProduct) {
20 | updatedCart = state.cart.map(item =>
21 | item.id === payload.product.id
22 | ? { ...item, quantity: item.quantity + 1 }
23 | : item
24 | );
25 | } else {
26 | updatedCart = [...state.cart, { ...payload.product, quantity: 1 }];
27 | }
28 |
29 | return {
30 | ...state,
31 | cart: updatedCart,
32 | totalPrice: calculateTotal(updatedCart),
33 | };
34 | }
35 |
36 | case ActionTypes.REMOVE_FROM_CART:
37 | updatedCart = state.cart.filter(item => item.id !== payload.id);
38 | return {
39 | ...state,
40 | cart: updatedCart,
41 | totalPrice: calculateTotal(updatedCart),
42 | };
43 |
44 | case ActionTypes.INCREASE_QUANTITY:
45 | updatedCart = state.cart.map(item =>
46 | item.id === payload.id
47 | ? { ...item, quantity: item.quantity + 1 }
48 | : item
49 | );
50 | return {
51 | ...state,
52 | cart: updatedCart,
53 | totalPrice: calculateTotal(updatedCart),
54 | };
55 |
56 | case ActionTypes.DECREASE_QUANTITY:
57 | updatedCart = state.cart
58 | .map(item =>
59 | item.id === payload.id
60 | ? { ...item, quantity: item.quantity - 1 }
61 | : item
62 | )
63 | .filter(item => item.quantity > 0);
64 | return {
65 | ...state,
66 | cart: updatedCart,
67 | totalPrice: calculateTotal(updatedCart),
68 | };
69 |
70 | default:
71 | return state;
72 | }
73 | };
74 |
--------------------------------------------------------------------------------
/src/genai/conversation.py:
--------------------------------------------------------------------------------
1 | from langchain.text_splitter import RecursiveCharacterTextSplitter
2 | import os
3 | from langchain_google_genai import GoogleGenerativeAIEmbeddings
4 | import google.generativeai as genai
5 | from langchain.vectorstores import FAISS
6 | from langchain_google_genai import ChatGoogleGenerativeAI
7 | from langchain.chains.question_answering import load_qa_chain
8 | from langchain.prompts import PromptTemplate
9 | from dotenv import load_dotenv
10 |
11 | load_dotenv()
12 | os.getenv("GOOGLE_API_KEY")
13 | genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
14 |
15 | def get_text_chunks(text):
16 | text_splitter = RecursiveCharacterTextSplitter(chunk_size=10000, chunk_overlap=1000)
17 | chunks = text_splitter.split_text(text)
18 | return chunks
19 |
20 |
21 | def get_vector_store(text_chunks):
22 | embeddings = GoogleGenerativeAIEmbeddings(model = "models/embedding-001")
23 | vector_store = FAISS.from_texts(text_chunks, embedding=embeddings)
24 | vector_store.save_local("faiss_index")
25 |
26 |
27 | def get_conversational_chain():
28 |
29 | prompt_template = """
30 | Answer the question as detailed as possible from the provided context, make sure to provide all the details, if the answer is not in
31 | provided context just say, "answer is not available in the context", don't provide the wrong answer\n\n
32 | Context:\n {context}?\n
33 | Question: \n{question}\n
34 |
35 | Answer:
36 | """
37 |
38 | model = ChatGoogleGenerativeAI(model="gemini-pro",
39 | temperature=0.3)
40 |
41 | prompt = PromptTemplate(template = prompt_template, input_variables = ["context", "question"])
42 | chain = load_qa_chain(model, chain_type="stuff", prompt=prompt)
43 |
44 | return chain
45 |
46 |
47 |
48 | def user_input(user_question):
49 | embeddings = GoogleGenerativeAIEmbeddings(model = "models/embedding-001")
50 |
51 | new_db = FAISS.load_local("faiss_index", embeddings)
52 | docs = new_db.similarity_search(user_question)
53 |
54 | chain = get_conversational_chain()
55 |
56 |
57 | response = chain(
58 | {"input_documents":docs, "question": user_question}
59 | , return_only_outputs=True)
60 |
61 | print(response)
62 | st.write("Reply: ", response["output_text"])
63 |
64 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
18 |
19 |
28 | React App
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # E-commerce Website using React and Redux
2 |
3 | Welcome to the E-commerce Website project! This application is designed to showcase various features of React and Redux for building a modern and responsive e-commerce platform.
4 |
5 | ## Project Features
6 |
7 | - **Fetch Products Using API:** The app fetches product data from an external API, providing a wide range of products for users to explore.
8 |
9 | - **Redux for State Management:** Utilizes Redux for efficient state management, ensuring a consistent and scalable data flow within the application.
10 |
11 | - **Add and Remove Functionality in Cart:** Users can easily add products to their shopping cart and remove them, providing a seamless shopping experience.
12 |
13 | - **React Class-Based Components:** The project uses class-based components in React, showcasing different component lifecycles and state management techniques.
14 |
15 | ## Getting Started
16 |
17 | To get started with this project, follow these steps:
18 |
19 |
20 | 1. Fork and clone this repository to your local machine:
21 |
22 | ```bash
23 | git clone https://github.com/your-username/E-commerce-website-using-React-Redux.git
24 | ```
25 | 2. Install the required dependencies:
26 |
27 | ```bash
28 | cd E-commerce-website-using-React-Redux
29 | npm install
30 | ```
31 | 3. Start the development server:
32 | ```bash
33 | npm start
34 | ```
35 |
36 | 4. Open [http://localhost:3000](http://localhost:3000) to view it in your browser. The page reloads when you make changes.
37 |
38 | You may also see any lint errors in the console.
39 |
40 | ## Scripts
41 |
42 | ### `npm test`
43 |
44 | Launches the test runner in the interactive watch mode.\
45 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
46 |
47 | ### `npm run build`
48 |
49 | Builds the app for production to the `build` folder.\
50 | It correctly bundles React in production mode and optimizes the build for the best performance.
51 |
52 | ## Snapshot
53 |
54 |
55 |
56 |
57 | ## Contributing
58 |
59 | We welcome contributions from the community.
60 |
61 | ------------------------------------------------------------------------
62 |
63 | Feel free to explore the codebase, test the application, and contribute to its development. If you have any questions or need assistance, please don't hesitate to reach out.
64 |
65 | Happy coding!
66 |
--------------------------------------------------------------------------------
/src/pages/Product/pages/Product.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | class Product extends React.Component {
4 | constructor() {
5 | super();
6 | this.state = {
7 | products: [],
8 | isLoading: true,
9 | failure: false,
10 | showAlert: false,
11 | };
12 | }
13 |
14 | componentDidMount() {
15 | const { fetchProducts } = this.props;
16 | fetchProducts();
17 | }
18 |
19 | componentDidUpdate(prevProps) {
20 | const { productReducer } = this.props;
21 | if (
22 | productReducer.status !== prevProps.productReducer.status &&
23 | productReducer.status === "reject"
24 | ) {
25 | this.setState({
26 | isLoading: false,
27 | failure: true,
28 | showAlert: false,
29 | });
30 | }
31 |
32 | if (
33 | productReducer.status !== prevProps.productReducer.status &&
34 | productReducer.status === "success"
35 | ) {
36 | this.setState({
37 | products: productReducer.products,
38 | isLoading: false,
39 | });
40 | }
41 | }
42 |
43 | handleAddToCart = (product) => {
44 | const { addToCart } = this.props;
45 | this.setState({ showAlert: true });
46 | addToCart(product);
47 | };
48 |
49 | render() {
50 | const { products, isLoading, showAlert } = this.state;
51 |
52 | return (
53 |
54 | {!isLoading ? (
55 |
56 | {showAlert && (
57 |
58 | Product added to cart
59 |
66 |
67 | )}
68 |
69 | {products.map((product) => (
70 |
71 |

72 |
73 | {product.title.slice(0, 20)}
74 |
75 |
{product.price} Rs
76 |
82 |
83 | ))}
84 |
85 |
86 | ) : (
87 |
88 |
89 | Loading...
90 |
91 |
92 | )}
93 |
94 | );
95 | }
96 | }
97 |
98 | export default Product;
99 |
--------------------------------------------------------------------------------
/src/pages/Cart/pages/Cart.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import CartProductCard from "../../../componets/CardProducts";
3 |
4 | export default class Cart extends Component {
5 | constructor() {
6 | super();
7 | this.state = {
8 | email: "",
9 | password: "",
10 | address: "",
11 | city: "",
12 | zip: "",
13 | errors: {},
14 | };
15 | }
16 |
17 | handleInputChange = (e) => {
18 | const { name, value } = e.target;
19 | this.setState({
20 | [name]: value,
21 | });
22 | };
23 |
24 | handleRemove = (id) => {
25 | this.props.removeFromCart(id);
26 | };
27 |
28 | handleIncreaseQuantity = (id) => {
29 | this.props.increaseQuantity(id);
30 | };
31 |
32 | handleDecreaseQuantity = (id) => {
33 | this.props.decreaseQuantity(id);
34 | };
35 |
36 | handleSubmit = (e) => {
37 | e.preventDefault();
38 | const { email, password, address, city, zip } = this.state;
39 | const errors = {};
40 |
41 | // Validations
42 | if (!email.includes("@")) {
43 | errors.email = "Please enter a valid email address";
44 | }
45 | if (password.length < 8) {
46 | errors.password = "Password must be at least 8 characters long";
47 | }
48 | if (address.trim() === "") {
49 | errors.address = "Address is required";
50 | }
51 | if (city.trim() === "") {
52 | errors.city = "City is required";
53 | }
54 | if (zip.trim() === "") {
55 | errors.zip = "Zip code is required";
56 | }
57 |
58 | // If no errors, proceed
59 | if (Object.keys(errors).length === 0) {
60 | console.log("Form is valid. Proceed with checkout.");
61 | } else {
62 | this.setState({ errors });
63 | }
64 | };
65 |
66 | render() {
67 | const { errors } = this.state;
68 | const { cart, totalPrice } = this.props.cartReducer;
69 |
70 | return (
71 |
72 |
73 |
Your Cart
74 | {cart.length > 0 ? (
75 | cart.map((product) => (
76 |
83 | ))
84 | ) : (
85 |
Your cart is empty
86 | )}
87 |
88 |
89 |
90 |
Total Price: ${totalPrice.toFixed(2)}
91 |
162 |
163 |
164 | );
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family : Tahoma, sans-serif;
4 | -webkit-font-smoothing: antialiased;
5 | -moz-osx-font-smoothing: grayscale;
6 | padding: 20px;
7 | background: whitesmoke;
8 | }
9 | img {
10 | height: 80px;
11 | }
12 | .custom-card {
13 | background: white;
14 | padding: 20px;
15 | text-align: center;
16 | border-radius: 10px;
17 | }
18 | .product-title {
19 | font-size: 18px;
20 | }
21 | .productsWrapper {
22 | display: grid;
23 | grid-template-columns: repeat(4, 1fr);
24 | gap: 30px;
25 | }
26 | .btn {
27 | border: none;
28 | outline: none;
29 | background: #764abc;
30 | padding: 5px 10px;
31 | color: #fff;
32 | border-radius: 5px;
33 | font-weight: bold;
34 | cursor: pointer;
35 | transition: all 0.3s ease-in-out;
36 | }
37 |
38 | .btn:hover {
39 | background: #513282;
40 | color: white;
41 | }
42 |
43 | .heading {
44 | padding: 25px 0;
45 | }
46 | .cartCount {
47 | font-weight: bold;
48 | text-transform: uppercase;
49 | margin-left: 40px;
50 | }
51 | .navLink {
52 | text-decoration: none;
53 | color: black;
54 | margin-left: 20px;
55 | }
56 |
57 | .cartCard {
58 | display: flex;
59 | align-items: center;
60 | justify-content: space-between;
61 | background: #fff;
62 | margin-bottom: 20px;
63 | padding: 14px;
64 | border-radius: 5px;
65 | }
66 |
67 | .center-block {
68 | display: block;
69 | margin-left: auto;
70 | margin-right: auto;
71 | }
72 |
73 | /* cart card css here */
74 |
75 | .cart-card {
76 | display: flex;
77 | justify-content: space-between;
78 | border: 1px solid #ddd;
79 | border-radius: 8px;
80 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
81 | margin: 10px 0;
82 | padding: 15px;
83 | transition: transform 0.3s ease;
84 | }
85 |
86 | .cart-card:hover {
87 | transform: translateY(-5px);
88 | box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15);
89 | }
90 |
91 | .cart-card-img {
92 | width: 100px;
93 | height: 100px;
94 | object-fit: cover;
95 | border-radius: 8px;
96 | margin-right: 15px;
97 | }
98 |
99 | .cart-card-body {
100 | flex-grow: 1;
101 | display: flex;
102 | flex-direction: column;
103 | justify-content: space-between;
104 | }
105 |
106 | .cart-card-title {
107 | font-size: 16px;
108 | font-weight: bold;
109 | margin: 0;
110 | }
111 |
112 | .cart-card-price {
113 | font-size: 14px;
114 | color: #3a3a3a;
115 | }
116 |
117 | .cart-card-controls {
118 | display: flex;
119 | align-items: center;
120 | gap: 1rem;
121 | margin-top: 10px;
122 | }
123 |
124 | .quantity-button {
125 | background-color: #f0f0f0;
126 | border: 1px solid #ccc;
127 | padding: 8px 12px;
128 | font-size: 18px;
129 | border-radius: 50%;
130 | cursor: pointer;
131 | transition: background-color 0.2s ease;
132 | }
133 |
134 | .quantity-button:hover {
135 | background-color: #ddd;
136 | }
137 |
138 | .product-quantity {
139 | font-size: 16px;
140 | font-weight: bold;
141 | margin: 0;
142 | }
143 |
144 | .cart-card-remove {
145 | background-color: #ff4d4f;
146 | color: white;
147 | padding: 8px 12px;
148 | font-size: 14px;
149 | border-radius: 5px;
150 | border: none;
151 | cursor: pointer;
152 | transition: background-color 0.3s ease;
153 | }
154 |
155 | .cart-card-remove:hover {
156 | background-color: #ff2d2f;
157 | }
158 |
159 | .cart-product-controls {
160 | display: flex;
161 | align-items: center;
162 | justify-content: center;
163 | gap: 10px;
164 | }
165 |
166 | .quantity-button {
167 | background-color: #f0f0f0;
168 | border: 1px solid #ccc;
169 | padding: 10px 15px;
170 | font-size: 18px;
171 | border-radius: 50%;
172 | cursor: pointer;
173 | transition: background-color 0.3s ease, transform 0.2s ease;
174 | display: flex;
175 | align-items: center;
176 | justify-content: center;
177 | }
178 |
179 | .quantity-button:hover {
180 | background-color: #ddd;
181 | }
182 |
183 | .quantity-button:active {
184 | transform: scale(0.95);
185 | }
186 |
187 | .product-quantity {
188 | font-size: 16px;
189 | font-weight: bold;
190 | margin: 0;
191 | padding: 0 10px;
192 | min-width: 30px;
193 | text-align: center;
194 | }
195 |
196 | .cart-card-remove {
197 | background-color: #ff4d4f;
198 | color: white;
199 | padding: 8px 15px;
200 | font-size: 14px;
201 | border-radius: 5px;
202 | border: none;
203 | cursor: pointer;
204 | transition: background-color 0.3s ease;
205 | }
206 |
207 | .cart-card-remove:hover {
208 | background-color: #ff2d2f;
209 | }
210 |
211 | .cart-card-remove:active {
212 | transform: scale(0.95);
213 | }
214 |
215 | /* Total card css */
216 |
217 | .card {
218 | display: flex;
219 | flex-direction: column;
220 | background-color: white;
221 | border: 1px solid #e2e8f0;
222 | box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.05);
223 | border-radius: 0.75rem;
224 | padding: 1rem;
225 | max-width: 300px;
226 | }
227 |
228 | .card-title {
229 | font-size: 1.125rem;
230 | font-weight: bold;
231 | color: #2d3748;
232 | }
233 |
234 | .card-subtitle {
235 | margin-top: 0.25rem;
236 | font-size: 0.75rem;
237 | font-weight: medium;
238 | text-transform: uppercase;
239 | color: #718096;
240 | }
241 |
242 | .card-content {
243 | margin-top: 0.5rem;
244 | color: #4a5568;
245 | }
246 |
247 | .card-link {
248 | margin-top: 1rem;
249 | display: inline-flex;
250 | align-items: center;
251 | gap: 0.25rem;
252 | font-size: 0.875rem;
253 | font-weight: 600;
254 | border-radius: 0.375rem;
255 | border: none;
256 | color: #3182ce;
257 | text-decoration: underline;
258 | transition: color 0.2s ease-in-out;
259 | }
260 |
261 | .card-link:hover {
262 | color: #2b6cb0;
263 | text-decoration: underline;
264 | }
265 |
266 | .card-link:focus {
267 | outline: none;
268 | color: #2b6cb0;
269 | text-decoration: underline;
270 | }
271 |
272 | .card-link.disabled {
273 | opacity: 0.5;
274 | pointer-events: none;
275 | }
276 |
277 | .card-icon {
278 | width: 16px;
279 | height: 16px;
280 | fill: none;
281 | stroke: currentColor;
282 | }
283 |
284 |
285 | .cart-container {
286 | display: flex;
287 | justify-content: space-between;
288 | gap: 2rem;
289 | margin-top: 2rem;
290 | }
291 |
292 | .cart-items {
293 | flex: 1;
294 | }
295 |
296 | .checkout-section {
297 | flex: 1;
298 | background-color: white;
299 | border-radius: 8px;
300 | padding: 20px;
301 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
302 | }
303 |
304 | h2, h3 {
305 | font-size: 1.5rem;
306 | color: #333;
307 | }
308 |
309 | h3 {
310 | color: #007bff;
311 | }
312 |
313 | .form-group {
314 | margin-bottom: 1.5rem;
315 | }
316 |
317 | label {
318 | display: block;
319 | font-weight: bold;
320 | margin-bottom: 0.5rem;
321 | }
322 |
323 | .form-input {
324 | width: 100%;
325 | padding: 10px;
326 | border-radius: 5px;
327 | border: 1px solid #ccc;
328 | font-size: 1rem;
329 | }
330 |
331 | .input-error {
332 | border-color: red;
333 | }
334 |
335 | .error-message {
336 | color: red;
337 | font-size: 0.875rem;
338 | margin-top: 0.25rem;
339 | }
340 |
341 | .checkout-btn {
342 | width: 100%;
343 | padding: 10px;
344 | background-color: #007bff;
345 | color: white;
346 | font-size: 1.1rem;
347 | border: none;
348 | border-radius: 5px;
349 | cursor: pointer;
350 | }
351 |
352 | .checkout-btn:hover {
353 | background-color: #0056b3;
354 | }
355 |
356 | /* Mobile Responsiveness */
357 | @media (max-width: 768px) {
358 | .cart-container {
359 | flex-direction: column;
360 | }
361 | .checkout-section {
362 | margin-top: 2rem;
363 | }
364 | }
--------------------------------------------------------------------------------