Skip to content

Commit

Permalink
🎨 Product Listing
Browse files Browse the repository at this point in the history
  • Loading branch information
nmdra committed Sep 11, 2024
1 parent 589b80b commit ca128b3
Show file tree
Hide file tree
Showing 8 changed files with 456 additions and 4 deletions.
82 changes: 82 additions & 0 deletions backend/controllers/userShopController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import Shop from '../models/shopModel.js';
import asyncHandler from '../middlewares/asyncHandler.js'
import mongoose from 'mongoose';


export const getShops = asyncHandler(async (req, res) => {
try {
const shops = await Shop.find() // Fetch only the shops belonging to the logged-in farmer
res.json(shops)
} catch (error) {
res.status(500)
throw new Error('Failed to fetch shops: ' + error.message)
}
})

// Fetch shop details and its products
export const getShopById = asyncHandler(async (req, res) => {
const { id } = req.params;

if (!mongoose.Types.ObjectId.isValid(id)) {
res.status(400);
throw new Error('Invalid Shop ID');
}

const shop = await Shop.findById(id).populate('products'); // Fetch shop and populate products

if (shop) {
res.json(shop);
} else {
res.status(404);
throw new Error('Shop not found');
}
});

// Fetch all products of a specific shop
export const getShopProducts = asyncHandler(async (req, res) => {
const { id } = req.params;

if (!mongoose.Types.ObjectId.isValid(id)) {
res.status(400);
throw new Error('Invalid Shop ID');
}

const shop = await Shop.findById(id).select('products'); // Fetch shop's products only

if (shop) {
res.json(shop.products);
} else {
res.status(404);
throw new Error('Shop not found');
}
});

// Fetch a single product from a shop by product ID
export const getShopProductById = asyncHandler(async (req, res) => {
const { id, productId } = req.params;

if (!mongoose.Types.ObjectId.isValid(id)) {
res.status(400);
throw new Error('Invalid Shop ID');
}

if (!mongoose.Types.ObjectId.isValid(productId)) {
res.status(400);
throw new Error('Invalid Product ID');
}

const shop = await Shop.findById(id);

if (shop) {
const product = shop.products.id(productId);
if (product) {
res.json(product);
} else {
res.status(404);
throw new Error('Product not found');
}
} else {
res.status(404);
throw new Error('Shop not found');
}
});
27 changes: 27 additions & 0 deletions backend/routes/userShopRoute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import express from 'express';
import {
getShopById,
getShopProducts,
getShopProductById,
getShops,
} from '../controllers/userShopController.js'; // Import the controller functions

const router = express.Router();

// Route to fetch details of a specific shop by ID
// GET /api/shops/:id
router.get('/', getShops);

// Route to fetch details of a specific shop by ID
// GET /api/shops/:id
router.get('/:id', getShopById);

// Route to fetch all products for a specific shop
// GET /api/shops/:id/products
router.get('/:id/products', getShopProducts);

// Route to fetch a specific product from a shop by product ID
// GET /api/shops/:id/products/:productId
router.get('/:id/products/:productId', getShopProductById);

export default router;
2 changes: 2 additions & 0 deletions backend/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import userRoute from './routes/userRoute.js'
import orderRoute from './routes/orderRoute.js'
import farmerRoutes from './routes/farmerRoute.js'
import shopRoute from './routes/shop_productRoute.js'
import userShop from './routes/userShopRoute.js'
import imageHandler from './routes/imageHandlerRoute.js'
import { errorHandler, notFound } from './middlewares/errorMiddleware.js'

Expand Down Expand Up @@ -36,6 +37,7 @@ app.use('/api/orders', orderRoute)
// Shop API routes
app.use('/api/farmers', farmerRoutes);
app.use('/api/shops', shopRoute);
app.use('/api/userShops', userShop);
app.use('/api/images', imageHandler)

app.use(notFound)
Expand Down
22 changes: 18 additions & 4 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ import ShopProfile from './Pages/farmer/shopProfile'
import Products from './Pages/farmer/products'
import AddProduct from './Pages/farmer/addProduct'
import UpdateProduct from './Pages/farmer/UpdateProduct'
import ShopList from './Pages/Shop/ShopList'
import ShopPage from './Pages/Shop/ShopPage'
import ProductPage from './Pages/Shop/ProductPage'

const router = createBrowserRouter(
createRoutesFromElements(
Expand Down Expand Up @@ -76,19 +79,30 @@ const router = createBrowserRouter(
/>
</Route>
</Route>

{/* Product Listing */}
<Route path="/shops" element={<ShopList />} />
<Route path="/shop/:id" element={<ShopPage />} />
<Route
path="/shop/:id/product/:productId"
element={<ProductPage />}
/>


{/* Shop & Farmer Routes */}
<Route path="/farmerLogin" element={<FarmerLogin />} />
<Route path="/farmerRegister" element={<FarmerRegister />} />
<Route path="/farmerdashboard" element={<FarmerDashboard />} />
<Route path="/farmerprofile" element={<ProfilePage />} />
<Route path="/myshops" element={<MyShop />} />
<Route path="/shopcreate" element={<AddShop />} />
<Route path="/shop/:id" element={<Shop />} />
<Route path="/shop/profile" element={<ShopProfile />} />
<Route path="/shop/:id/productpage" element={<Products />} />
<Route path="/farmerShopcreate" element={<AddShop />} />
<Route path="/farmerShop/:id" element={<Shop />} />
<Route path="/farmerShop/profile" element={<ShopProfile />} />
<Route path="/farmerShop/:id/productpage" element={<Products />} />
<Route path="/addproduct" element={<AddProduct />} />
<Route path="/updateproduct" element={<UpdateProduct />} />
<Route path="/farmerlogout" element={<FarmerLogout />} />

<Route path="*" element={<NotFound />} />
</Route>
)
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/Pages/HomePage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ const Homepage = () => {
>
Register
</Link>
<Link
to="/shops"
className="px-6 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600"
>
Shops
</Link>
</div>
</div>
</div>
Expand Down
81 changes: 81 additions & 0 deletions frontend/src/Pages/Shop/ProductPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { useEffect, useState } from 'react';

Check failure on line 1 in frontend/src/Pages/Shop/ProductPage.jsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

frontend/src/Pages/Shop/ProductPage.jsx#L1

[no-unused-vars] 'React' is defined but never used.
import axios from 'axios';
import { useParams } from 'react-router-dom';

const ProductPage = ({ onAddToCart }) => {

Check failure on line 5 in frontend/src/Pages/Shop/ProductPage.jsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

frontend/src/Pages/Shop/ProductPage.jsx#L5

[react/prop-types] 'onAddToCart' is missing in props validation
const { id, productId } = useParams(); // shop ID and product ID from URL
const [product, setProduct] = useState({});

useEffect(() => {
// Fetch product details
const fetchProductDetails = async () => {
try {
const productResponse = await axios.get(`/api/userShops/${id}/products/${productId}`);
setProduct(productResponse.data);
} catch (error) {
console.error('Failed to fetch product details', error);
}
};

fetchProductDetails();
}, [id, productId]);

const handleAddToCart = () => {
// Add the product to the cart using the parent function
onAddToCart(product);
};

return (
<section className="text-gray-600 body-font overflow-hidden">
<div className="container px-5 py-24 mx-auto">
<div className="lg:w-4/5 mx-auto flex flex-wrap">
<img
alt={product.name}
className="lg:w-1/2 w-full lg:h-auto h-64 object-cover object-center rounded"
src={product.image || 'https://dummyimage.com/400x400'} // Placeholder if no image
/>
<div className="lg:w-1/2 w-full lg:pl-10 lg:py-6 mt-6 lg:mt-0">
<h2 className="text-sm title-font text-gray-500 tracking-widest">{product.brand || 'Brand Name'}</h2>
<h1 className="text-gray-900 text-3xl title-font font-medium mb-1">{product.name || 'Product Name'}</h1>
<p className="leading-relaxed mb-4">{product.description || 'Product description goes here.'}</p>
<p className="text-lg font-semibold">Price: LKR {product.pricePerKg?.toFixed(2) || '0.00'}</p>
<div className="flex mt-6 items-center pb-5 border-b-2 border-gray-100 mb-5">
<div className="flex ml-6 items-center">
<span className="mr-3">Size</span>
<select className="rounded border appearance-none border-gray-300 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-200 focus:border-indigo-500 text-base pl-3 pr-10">
<option>500g</option>
<option>1Kg</option>
<option>2Kg</option>
<option>3Kg</option>
</select>
</div>
</div>
<div className="flex">
<span className="title-font font-medium text-2xl text-gray-900">LKR {product.pricePerKg?.toFixed(2) || '0.00'}</span>
<button
onClick={handleAddToCart}
className="flex ml-auto text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded"
>
Add to Cart
</button>
<button className="rounded-full w-10 h-10 bg-gray-200 p-0 border-0 inline-flex items-center justify-center text-gray-500 ml-4">
<svg
fill="currentColor"
stroke-linecap="round"

Check failure on line 64 in frontend/src/Pages/Shop/ProductPage.jsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

frontend/src/Pages/Shop/ProductPage.jsx#L64

[react/no-unknown-property] Unknown property 'stroke-linecap' found, use 'strokeLinecap' instead
stroke-linejoin="round"

Check failure on line 65 in frontend/src/Pages/Shop/ProductPage.jsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

frontend/src/Pages/Shop/ProductPage.jsx#L65

[react/no-unknown-property] Unknown property 'stroke-linejoin' found, use 'strokeLinejoin' instead
stroke-width="2"

Check failure on line 66 in frontend/src/Pages/Shop/ProductPage.jsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

frontend/src/Pages/Shop/ProductPage.jsx#L66

[react/no-unknown-property] Unknown property 'stroke-width' found, use 'strokeWidth' instead
className="w-5 h-5"
viewBox="0 0 24 24"
>
<path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
</section>
);
};

export default ProductPage;
Loading

0 comments on commit ca128b3

Please sign in to comment.