Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/app/api/services/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ import { getServices } from '../../../queries/services';

export interface ServicesFilters {
serviceStatus?: ServiceStatusEnum;
allowedTokens?: any;
buyerId?: string | null;
sellerId?: string | null;
numberPerPage?: number | null;
offset?: number | null;
searchQuery?: string | null;
platformId?: string | null;
keywordList?: string[];
selectedToken?: string | null;
minRate?: string | null;
maxRate?: string | null;
selectedRatings?: string[] | null;
}

/**
Expand Down
98 changes: 98 additions & 0 deletions src/components/ServiceFilterPopup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react';
import useAllowedTokens from '../hooks/useAllowedTokens';
import { IToken } from '../types';

interface ServiceFilterPopupProps {
filters: {
minRate: string;
maxRate: string;
selectedToken: string;
selectedRatings: string[];
};
setFilters: React.Dispatch<React.SetStateAction<{
minRate: string;
maxRate: string;
selectedToken: string;
selectedRatings: string[];
}>>;
handleResetFilter: () => void;
}

function ServiceFilterPopup({ filters, setFilters, handleResetFilter }: ServiceFilterPopupProps) {
const allowedTokens = useAllowedTokens();

const handleReset = () => {
handleResetFilter();
};

return (
<div className='absolute bg-base-200 border border-3 border-gray-300 text-base-content p-4 shadow-lg rounded-lg mt-2 ml-2 right-0 z-50'>
<div className='flex flex-col'>
{/* <label className='text-sm mt-1 font-bold'>Rate</label>
<div className='flex flex-row gap-2'>
<input
type='number'
value={minRate}
onChange={e => setMinRate(e.target.value)}
className='border border-3 border-gray-300 p-2 rounded w-24'
placeholder='Min'
/>
<input
type='number'
value={maxRate}
onChange={e => setMaxRate(e.target.value)}
className='border border-3 border-gray-300 p-2 rounded w-24'
placeholder='Max'
/>
</div> */}
<label className='text-sm mt-3 font-bold'>Token</label>
<div className='flex flex-col'>
{allowedTokens.map((token: IToken) => (
<div className='flex items-center gap-2' key={token.address}>
<input
type='radio'
name='token'
value={token.address}
checked={filters.selectedToken === token.address}
onChange={() => {
setFilters({ ...filters, selectedToken: token.address });
}}
/>
<label>{token.symbol}</label>
</div>
))}
</div>
{/* <label className='text-sm mt-3 font-bold'>Rating</label>
<div className='flex flex-col'>
{Array.from({ length: 5 }, (_, i) => i + 1).map((rating, i) => (
<div className='flex items-center gap-2' key={i}>
<input
type='checkbox'
value={rating.toString()}
checked={selectedRatings.includes(rating.toString())}
onChange={e => {
const rating = e.target.value;
setSelectedRatings(prevState =>
prevState.includes(rating)
? prevState.filter(r => r !== rating)
: [...prevState, rating],
);
}}
/>
<label>
{rating} star{rating == 1 ? '' : 's'}
</label>
</div>
))}
</div> */}
</div>
<div className='mt-4'>
<button className='px-5 py-2 border border-black rounded-lg' onClick={handleReset}>
Reset
</button>
</div>
</div>
);
}

export default ServiceFilterPopup;
95 changes: 73 additions & 22 deletions src/components/ServiceList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IService, ServiceStatusEnum } from '../types';
import Loading from './Loading';
import ServiceItem from './ServiceItem';
import SearchServiceButton from './Form/SearchServiceButton';
import ServiceFilterPopup from './ServiceFilterPopup';

function ServiceList() {
const { builderPlace } = useContext(BuilderPlaceContext);
Expand All @@ -14,16 +15,36 @@ function ServiceList() {
const query = router.query;
const searchQuery = query.search as string;
const [view, setView] = useState(1);
const [isPopupVisible, setPopupVisible] = useState(false);

// Consolidated state for filters
const [filters, setFilters] = useState({
minRate: '',
maxRate: '',
selectedToken: '',
selectedRatings: [''],
});

const { hasMoreData, services, loading, loadMore } = useFilteredServices(
ServiceStatusEnum.Opened,
undefined,
undefined,
searchQuery?.toLocaleLowerCase(),
PAGE_SIZE,
filters,
builderPlace?.talentLayerPlatformId,
);

const handleResetFilter = () => {
setFilters({
minRate: '',
maxRate: '',
selectedToken: '',
selectedRatings: [''],
});
setPopupVisible(false);
};

return (
<>
{searchQuery && services.length > 0 && (
Expand Down Expand Up @@ -54,29 +75,53 @@ function ServiceList() {
Table View
</button>

<button className='px-4 py-2 rounded-full ml-auto hidden text-base-content border mr-2'>

<button
className='px-4 py-2 rounded-full ml-auto md:hidden text-base-content border mr-2'
onClick={() => setPopupVisible(!isPopupVisible)}>
Filter
</button>
</div>

<button className='hidden px-4 py-2 rounded-full ml-auto text-base-content border mr-2'>
Filter
</button>

{/* Filter */}
<div className='relative ml-auto'>
<button
className='hidden md:block px-4 py-2 rounded-full ml-auto text-base-content border mr-2'
onClick={() => setPopupVisible(!isPopupVisible)}>
Filter
</button>
{isPopupVisible && (
<ServiceFilterPopup
filters={filters}
setFilters={setFilters}
handleResetFilter={handleResetFilter}
/>
)}
</div>
<div className='mt-2 md:mt-0 flex justify-end'>
<SearchServiceButton value={searchQuery} />
</div>
</div>

{view === 1 &&
services.map((service: IService, i: number) => (
<ServiceItem
service={service}
embedded={router.asPath.includes('embed/')}
key={i}
view={view}
/>
))}
{view === 1 && (
<>
{services.length > 0 ? (
services.map((service: IService, i: number) => (
<ServiceItem
service={service}
embedded={router.asPath.includes('embed/')}
key={i}
view={view}
/>
))
) : (
<p className='text-xl text-base-content font-medium tracking-wider flex justify-center items-center'>
No services found
</p>
)}
</>
)}

{view === 2 && (
<table className='min-w-full text-center'>
Expand All @@ -90,14 +135,20 @@ function ServiceList() {
</tr>
</thead>
<tbody>
{services.map((service: IService, i: number) => (
<ServiceItem
service={service}
embedded={router.asPath.includes('embed/')}
key={i}
view={view}
/>
))}
{services.length > 0 ? (
services.map((service: IService, i: number) => (
<ServiceItem
service={service}
embedded={router.asPath.includes('embed/')}
key={i}
view={view}
/>
))
) : (
<span className='text-xl text-base-content font-medium tracking-wider flex justify-center items-center'>
No services found
</span>
)}
</tbody>
</table>
)}
Expand All @@ -107,7 +158,7 @@ function ServiceList() {
<button
type='submit'
className={`px-5 py-2 mt-5 content-center border-2 text-base-content border-black rounded-xl font-medium text-content
`}
`}
disabled={!hasMoreData}
onClick={() => loadMore()}>
Load More Posts
Expand Down
14 changes: 9 additions & 5 deletions src/hooks/useFilteredServices.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { useEffect, useState } from 'react';
import { IService, ServiceStatusEnum } from '../types';
import { Filters, IService, ServiceStatusEnum } from '../types';
import { getFilteredServicesByKeywords } from '../pages/api/services/request';
import { useChainId } from 'wagmi';
import useAllowedTokens from './useAllowedTokens';

const useFilteredServices = (
serviceStatus?: ServiceStatusEnum,
buyerId?: string,
sellerId?: string,
searchQuery?: string,
numberPerPage?: number,
filters?: Filters,
platformId?: string,
): {
hasMoreData: boolean;
Expand All @@ -21,6 +23,7 @@ const useFilteredServices = (
const [loading, setLoading] = useState(false);
const [offset, setOffset] = useState(0);
const chainId = useChainId();
const allowedTokens = useAllowedTokens();

useEffect(() => {
setServices([]);
Expand All @@ -35,13 +38,15 @@ const useFilteredServices = (

response = await getFilteredServicesByKeywords(
serviceStatus,
allowedTokens,
buyerId,
sellerId,
numberPerPage,
offset,
searchQuery,
platformId,
chainId,
filters
);

const newServices = response?.data?.services;
Expand All @@ -51,26 +56,25 @@ const useFilteredServices = (
} else {
setServices([...services, ...newServices]);
}
if (numberPerPage && newServices.length < numberPerPage) {
if (newServices && numberPerPage && newServices.length < numberPerPage) {
setHasMoreData(false);
} else {
setHasMoreData(true);
}
} catch (err: any) {
// eslint-disable-next-line no-console
console.error(err);
} finally {
setLoading(false);
}
};
fetchData();
}, [numberPerPage, offset, searchQuery]);
}, [numberPerPage, offset, searchQuery, filters]);

const loadMore = () => {
numberPerPage ? setOffset(offset + numberPerPage) : '';
};

return { hasMoreData: hasMoreData, services, loading, loadMore };
return { hasMoreData, services, loading, loadMore };
};

export default useFilteredServices;
14 changes: 11 additions & 3 deletions src/pages/api/services/filtered.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { getServices } from '../../../queries/services';
import keywordFilter from './filter.json';
import { ServiceStatusEnum } from '../../../types';
import { IToken, ServiceStatusEnum } from '../../../types';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const query = req.query;
// @dev : here you can add the filter logic
let keywordList = keywordFilter.keywords;

const serviceStatus = query.serviceStatus as ServiceStatusEnum;
Expand All @@ -16,17 +15,26 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const searchQuery = query.searchQuery as string;
const platformId = query.platformId as string;
const chainId = Number(query.chainId);

const allowedTokens = JSON.parse(query.allowedTokens as string);
const selectedToken = query.selectedToken as string;
const minRate = query.minRate as string;
const maxRate = query.maxRate as string;
const selectedRatings = JSON.parse(query.selectedRatings as string);
try {
let response = await getServices(chainId, {
serviceStatus,
allowedTokens,
buyerId,
sellerId,
numberPerPage,
offset,
keywordList,
searchQuery,
platformId,
selectedToken,
minRate,
maxRate,
selectedRatings,
});

const filteredServices = response?.data?.data?.services;
Expand Down
Loading