├── 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 |
--------------------------------------------------------------------------------