├── .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 |
9 | 10 |
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 | {product.title} 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 | Screenshot 2023-08-13 at 11 05 22 PM 55 | Screenshot 2023-08-13 at 11 05 49 PM 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 |
92 |
93 | 94 | 102 | {errors.email &&

{errors.email}

} 103 |
104 | 105 |
106 | 107 | 115 | {errors.password &&

{errors.password}

} 116 |
117 | 118 |
119 | 120 | 129 | {errors.address &&

{errors.address}

} 130 |
131 | 132 |
133 | 134 | 142 | {errors.city &&

{errors.city}

} 143 |
144 | 145 |
146 | 147 | 155 | {errors.zip &&

{errors.zip}

} 156 |
157 | 158 | 161 |
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 | } --------------------------------------------------------------------------------