├── src ├── main.jsx ├── App.css ├── index.css ├── App.jsx ├── server.js └── assets │ └── react.svg ├── index.html ├── README.md ├── package.json └── public └── vite.svg /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # E-commerce Top Products Microservice 2 | 3 | This project is a full-stack application that allows users to retrieve and display the top products within a specified category and price range from multiple e-commerce companies. The application comprises a backend microservice built with Node.js and Express, and a frontend built with React. 4 | 5 | ## Features 6 | 7 | - Retrieve top N products within a category. 8 | - Support for pagination when N exceeds 10 products. 9 | - Sorting of products based on rating, price, company, or discount in ascending or descending order. 10 | - Unique identifier for each product. 11 | - Display product details on a specific product ID. 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-header { 6 | background-color: #282c34; 7 | min-height: 100vh; 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | justify-content: center; 12 | font-size: calc(10px + 2vmin); 13 | color: white; 14 | } 15 | 16 | .product-list { 17 | display: flex; 18 | flex-wrap: wrap; 19 | justify-content: center; 20 | } 21 | 22 | .product-item { 23 | background: white; 24 | color: black; 25 | border: 1px solid #ccc; 26 | margin: 10px; 27 | padding: 10px; 28 | border-radius: 5px; 29 | width: 200px; 30 | } 31 | 32 | label { 33 | margin: 0 10px; 34 | } 35 | 36 | input, select { 37 | margin: 0 5px; 38 | } 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "afford", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "express": "^4.19.2", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^18.2.66", 19 | "@types/react-dom": "^18.2.22", 20 | "@vitejs/plugin-react": "^4.2.1", 21 | "eslint": "^8.57.0", 22 | "eslint-plugin-react": "^7.34.1", 23 | "eslint-plugin-react-hooks": "^4.6.0", 24 | "eslint-plugin-react-refresh": "^0.4.6", 25 | "vite": "^5.2.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import axios from 'axios'; 3 | import './App.css'; 4 | 5 | const App = () => { 6 | const [products, setProducts] = useState([]); 7 | const [category, setCategory] = useState('laptops'); 8 | const [n, setN] = useState(10); 9 | const [sortBy, setSortBy] = useState('rating'); 10 | const [order, setOrder] = useState('desc'); 11 | const [page, setPage] = useState(1); 12 | 13 | useEffect(() => { 14 | fetchProducts(); 15 | }, [category, n, sortBy, order, page]); 16 | 17 | const fetchProducts = async () => { 18 | try { 19 | const response = await axios.get(`http://localhost:3001/categories/${category}/products`, { 20 | params: { n, sortBy, order, page } 21 | }); 22 | setProducts(response.data); 23 | } catch (error) { 24 | console.error('Error fetching products:', error); 25 | } 26 | }; 27 | 28 | return ( 29 |
30 |
31 |

Top Products

32 |
33 | 37 | 41 | 50 | 57 | 61 |
62 |
63 | {products.map(product => ( 64 |
65 |

{product.productName}

66 |

Price: ${product.price}

67 |

Rating: {product.rating}

68 |

Discount: {product.discount}%

69 |

Availability: {product.availability}

70 |
71 | ))} 72 |
73 |
74 |
75 | ); 76 | }; 77 | 78 | export default App; 79 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const axios = require('axios'); 3 | const { v4: uuidv4 } = require('uuid'); 4 | const app = express(); 5 | 6 | const PORT = 3001; 7 | const TEST_SERVER_URL = 'http://localhost:3001'; 8 | 9 | // Mock function to register with e-commerce companies 10 | async function registerWithEcommerceCompanies() { 11 | // Assume registration is done here 12 | } 13 | 14 | // Middleware to register with companies on server start 15 | app.use(async (req, res, next) => { 16 | await registerWithEcommerceCompanies(); 17 | next(); 18 | }); 19 | 20 | // Get top N products in a category with optional sorting and pagination 21 | app.get('/categories/:categoryname/products', async (req, res) => { 22 | const { categoryname } = req.params; 23 | const { n = 10, page = 1, sortBy = 'rating', order = 'desc' } = req.query; 24 | const limit = Math.min(n, 10); 25 | const offset = (page - 1) * limit; 26 | 27 | try { 28 | // Fetch products from each company 29 | const responses = await Promise.all([ 30 | axios.get(`${TEST_SERVER_URL}/company1/categories/${categoryname}/products`), 31 | axios.get(`${TEST_SERVER_URL}/company2/categories/${categoryname}/products`), 32 | axios.get(`${TEST_SERVER_URL}/company3/categories/${categoryname}/products`), 33 | axios.get(`${TEST_SERVER_URL}/company4/categories/${categoryname}/products`), 34 | axios.get(`${TEST_SERVER_URL}/company5/categories/${categoryname}/products`) 35 | ]); 36 | 37 | // Combine and sort products 38 | let products = responses.flatMap(response => response.data); 39 | products.sort((a, b) => { 40 | if (order === 'asc') { 41 | return a[sortBy] - b[sortBy]; 42 | } else { 43 | return b[sortBy] - a[sortBy]; 44 | } 45 | }); 46 | 47 | // Paginate 48 | const paginatedProducts = products.slice(offset, offset + limit); 49 | 50 | // Add unique ID to each product 51 | const result = paginatedProducts.map(product => ({ ...product, id: uuidv4() })); 52 | 53 | res.json(result); 54 | } catch (error) { 55 | res.status(500).json({ error: 'Error fetching products' }); 56 | } 57 | }); 58 | 59 | // Get product details by ID 60 | app.get('/categories/:categoryname/products/:productid', async (req, res) => { 61 | const { categoryname, productid } = req.params; 62 | 63 | try { 64 | // Fetch all products to find the specific product 65 | const responses = await Promise.all([ 66 | axios.get(`${TEST_SERVER_URL}/company1/categories/${categoryname}/products`), 67 | axios.get(`${TEST_SERVER_URL}/company2/categories/${categoryname}/products`), 68 | axios.get(`${TEST_SERVER_URL}/company3/categories/${categoryname}/products`), 69 | axios.get(`${TEST_SERVER_URL}/company4/categories/${categoryname}/products`), 70 | axios.get(`${TEST_SERVER_URL}/company5/categories/${categoryname}/products`) 71 | ]); 72 | 73 | const products = responses.flatMap(response => response.data); 74 | const product = products.find(p => p.id === productid); 75 | 76 | if (product) { 77 | res.json(product); 78 | } else { 79 | res.status(404).json({ error: 'Product not found' }); 80 | } 81 | } catch (error) { 82 | res.status(500).json({ error: 'Error fetching product details' }); 83 | } 84 | }); 85 | 86 | app.listen(PORT, () => { 87 | console.log(`Server running on port ${PORT}`); 88 | }); 89 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------