Skip to content

Commit

Permalink
Enhanced Listings on all 3 FE pages with Search, Sort, and Categories (
Browse files Browse the repository at this point in the history
…#475)

* faq?

* added form for adding new listing

* implemented edit listing page

* feFetching

* feFetching

* seedImages

* FE fetching implementation
  • Loading branch information
ShivanshPlays authored Nov 6, 2024
1 parent 12ac513 commit 377c9d2
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 121 deletions.
63 changes: 52 additions & 11 deletions scruter-nextjs/actions/seller/listing.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use server';
import prismadb from '@/lib/prismadb';
import { Image, Listing } from '@prisma/client';
import { Category, Image, Listing } from '@prisma/client';

export interface ListingWithImages extends Listing {
images: Image[];
Expand Down Expand Up @@ -184,24 +184,65 @@ export async function DeleteListing({
}
}

export async function GetAllListing(): Promise<{

export async function GetAllListing(
category?: Category,
searchQuery?: string,
sort?: "" | "asc" | "desc" | undefined
): Promise<{
success: boolean;
error?: string;
data?: ListingWithImages[];
}> {
const resp = await prismadb.listing.findMany({
include: {
images: true,
},
});

if (!resp) {
return { success: false, error: 'Error occured in fetching all listing' };
// Set no-cache headers to ensure fresh data
const headers = new Headers();
headers.append('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
headers.append('Pragma', 'no-cache');
headers.append('Expires', '0');

// Build the where clause based on provided parameters
let whereClause = {};

if (category) {
whereClause = { ...whereClause, category };
}

if (searchQuery) {
whereClause = {
...whereClause,
OR: [
{ name: { contains: searchQuery, mode: 'insensitive' } },
{ description: { contains: searchQuery, mode: 'insensitive' } },
],
};
}

return { success: true, data: resp };
try {
// Query the database for listings with optional filters and sorting
const resp: ListingWithImages[] = await prismadb.listing.findMany({
where: whereClause,
include: {
images: true,
},
orderBy: sort ? { price: sort } : undefined, // Sort by price if 'sort' is provided
});

if (!resp) {
return { success: false, error: 'Error occurred in fetching all listings' };
}

return { success: true, data: resp };
} catch (error: unknown) {
// Narrowing the error type to handle it properly
if (error instanceof Error) {
return { success: false, error: error.message || 'An error occurred' };
} else {
return { success: false, error: 'An unknown error occurred' };
}
}
}


export async function getSpecificListing({
listingId,
}: {
Expand Down
82 changes: 49 additions & 33 deletions scruter-nextjs/app/(routes)/food/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,28 @@ import axios from 'axios';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSearch } from '@fortawesome/free-solid-svg-icons'; // Import FontAwesomeIcon
import '../../globals.css'; // Ensure your global styles are imported
import { GetAllListing, ListingWithImages } from '@/actions/seller/listing';
import { Listing } from '@prisma/client';
import toast, { Toaster } from 'react-hot-toast';
import ListingCardFE from '@/components/listingCardFE';

const FoodPage: React.FC = () => {
const [foodItems, setFoodItems] = useState<any[]>([]);
const [foodItems, setFoodItems] = useState<ListingWithImages[]>([]);
const [loading, setLoading] = useState(true);
const [query, setQuery] = useState('');
const [type, setType] = useState('food'); // Default search type is 'food'
const [sort, setSort] = useState('');
const [sort, setSort] = useState<"" | "asc" | "desc" | undefined>('');

useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
// Example API call to fetch food data
const foodResponse = await axios.get('/api/food', {
params: { query, type, sort },
});
const foodResponse = await GetAllListing('Fooding',query,sort);

if (!foodResponse || !foodResponse.data) {
toast.error('No data fetched from BE');
return;
}
setFoodItems(foodResponse.data);
} catch (error) {
console.error('Error fetching food data:', error);
Expand All @@ -29,11 +35,13 @@ const FoodPage: React.FC = () => {
}
};

//fetchData();
}, [query, type, sort]);
fetchData();
}, [query, sort]);

console.log(foodItems);
return (
<div className="bg-gray-50 text-gray-800">
<Toaster />
{/* Hero Section with Banner Image */}
<section
className="relative h-[60vh] bg-cover bg-center text-white"
Expand Down Expand Up @@ -87,9 +95,11 @@ const FoodPage: React.FC = () => {
id="sort-by-price"
name="sort"
value={sort}
onChange={e => setSort(e.target.value)}
onChange={e => {
const value = e.target.value as "" | "asc" | "desc" | undefined; // Type the value to be one of these options
setSort(value); // Update the sort state
}}
>
<option value="">Sort by Price</option>
<option value="asc">Low to High</option>
<option value="desc">High to Low</option>
</select>
Expand All @@ -111,43 +121,49 @@ const FoodPage: React.FC = () => {
{/* Food Items Section */}
<section className="py-16 bg-white">
<h2 className="text-2xl font-bold text-center mb-12">Available Food</h2>
{/*

<div
id="food-items"
className="max-w-4xl mx-auto grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8"
className="max-w-7xl mx-auto grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8"
>
{loading ? (
<div className="loading flex justify-center items-center h-48">
<div className="spinner border-4 border-t-4 border-blue-600 rounded-full w-10 h-10 animate-spin"></div>
</div>
) : (
foodItems.map((food) => (
<FoodCard key={food.id} food={food} />
foodItems.map(food => (
<ListingCardFE
key={food.id}
name={food.name}
price={food.price}
description={food.description}
images={food.images}
/>
))
)}
</div> */}
</div>
</section>
</div>
);
};

const FoodCard: React.FC<{ food: any }> = ({ food }) => (
<div className="bg-white p-6 rounded-lg shadow-md flex flex-col items-center">
<img
src={food.imageUrl}
alt={food.title}
className="w-full h-48 object-cover rounded-lg mb-4"
/>
<h3 className="text-xl font-bold text-gray-800 mb-4">{food.title}</h3>
<p className="text-gray-600 mb-4">{food.description}</p>
<p className="text-lg font-semibold text-gray-800 mb-4">{food.price}</p>
<a
href={`/food/${food.id}`}
className="text-blue-500 hover:text-blue-700 transition"
>
View Details
</a>
</div>
);
// const FoodCard: React.FC<{ food: any }> = ({ food }) => (
// <div className="bg-white p-6 rounded-lg shadow-md flex flex-col items-center">
// <img
// src={food.imageUrl}
// alt={food.title}
// className="w-full h-48 object-cover rounded-lg mb-4"
// />
// <h3 className="text-xl font-bold text-gray-800 mb-4">{food.title}</h3>
// <p className="text-gray-600 mb-4">{food.description}</p>
// <p className="text-lg font-semibold text-gray-800 mb-4">{food.price}</p>
// <a
// href={`/food/${food.id}`}
// className="text-blue-500 hover:text-blue-700 transition"
// >
// View Details
// </a>
// </div>
// );

export default FoodPage;
67 changes: 29 additions & 38 deletions scruter-nextjs/app/(routes)/for-sale/page.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,43 @@
'use client';

import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSearch } from '@fortawesome/free-solid-svg-icons'; // Import FontAwesomeIcon
import '../../globals.css'; // Ensure your global styles are imported
import { faSearch } from '@fortawesome/free-solid-svg-icons';
import toast, { Toaster } from 'react-hot-toast';
import { GetAllListing } from '@/actions/seller/listing';
import ListingCardFE from '@/components/listingCardFE';

const ForSalePage: React.FC = () => {
const [items, setItems] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [query, setQuery] = useState('');
const [type, setType] = useState('sale'); // Default search type is 'sale'
const [sort, setSort] = useState('');
const [sort, setSort] = useState<'asc' | 'desc' | ''>('');

useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
// Example API call to fetch for sale data
const response = await axios.get('/api/sale', {
params: { query, type, sort },
});
setItems(response.data);
// Fetching the items with search query and sort applied
const forSaleResp = await GetAllListing('For_Sale', query, sort);

if (!forSaleResp || !forSaleResp.data) {
toast.error('No data fetched from BE');
return;
}
setItems(forSaleResp.data);
} catch (error) {
console.error('Error fetching sale items:', error);
console.error('Error fetching data:', error);
} finally {
setLoading(false);
}
};

//fetchData();
}, [query, type, sort]);
fetchData();
}, [query, sort]); // Only depend on query and sort

return (
<div className="bg-gray-50 text-gray-800">
<Toaster />
{/* Hero Section with Banner Image */}
<section
className="relative h-[60vh] bg-cover bg-center text-white"
Expand Down Expand Up @@ -87,7 +91,7 @@ const ForSalePage: React.FC = () => {
id="sort-by-price"
name="sort"
value={sort}
onChange={e => setSort(e.target.value)}
onChange={e => setSort(e.target.value as 'asc' | 'desc' | '')}
>
<option value="">Sort by Price</option>
<option value="asc">Low to High</option>
Expand All @@ -113,43 +117,30 @@ const ForSalePage: React.FC = () => {
<h2 className="text-2xl font-bold text-center mb-12">
Available Items
</h2>
{/*

<div
id="items-for-sale"
className="max-w-4xl mx-auto grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8"
className="max-w-7xl mx-auto grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8"
>
{loading ? (
<div className="loading flex justify-center items-center h-48">
<div className="spinner border-4 border-t-4 border-blue-600 rounded-full w-10 h-10 animate-spin"></div>
</div>
) : (
items.map((item) => (
<ForSaleCard key={item.id} item={item} />
items.map(item => (
<ListingCardFE
key={item.id}
name={item.name}
price={item.price}
description={item.description}
images={item.images}
/>
))
)}
</div> */}
</div>
</section>
</div>
);
};

const ForSaleCard: React.FC<{ item: any }> = ({ item }) => (
<div className="bg-white p-6 rounded-lg shadow-md flex flex-col items-center">
<img
src={item.imageUrl}
alt={item.title}
className="w-full h-48 object-cover rounded-lg mb-4"
/>
<h3 className="text-xl font-bold text-gray-800 mb-4">{item.title}</h3>
<p className="text-gray-600 mb-4">{item.description}</p>
<p className="text-lg font-semibold text-gray-800 mb-4">{item.price}</p>
<a
href={`/sale/${item.id}`}
className="text-blue-500 hover:text-blue-700 transition"
>
View Details
</a>
</div>
);

export default ForSalePage;
Loading

0 comments on commit 377c9d2

Please sign in to comment.