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
19 changes: 16 additions & 3 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,22 @@ import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
images:{
domains: ['images.unsplash.com','randomuser.me'],
}
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.unsplash.com',
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'randomuser.me',
port: '',
pathname: '/api/portraits/**',
},
],
},
};

export default nextConfig;
96 changes: 86 additions & 10 deletions src/app/api/workers/route.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,94 @@
import { NextResponse } from 'next/server'
import { NextRequest, NextResponse } from 'next/server'
import workersData from '../../../../workers.json'
import { WorkerType } from '@/types/workers'

export async function GET() {
export async function GET(request: NextRequest) {
try {
return NextResponse.json({
success: true,
data: workersData
})
const { searchParams } = new URL(request.url)

// Extract query parameters
const page = parseInt(searchParams.get('page') || '1')
const limit = parseInt(searchParams.get('limit') || '12')
const services = searchParams.get('services')?.split(',').filter(Boolean) || []
const minPrice = parseInt(searchParams.get('minPrice') || '0')
const maxPrice = parseInt(searchParams.get('maxPrice') || '999999')
const sortBy = searchParams.get('sortBy') || 'name'

// Filter workers
const filteredWorkers: WorkerType[] = workersData
.filter((worker: WorkerType) => worker.pricePerDay > 0)
.filter((worker: WorkerType) => worker.id !== null)
.filter((worker: WorkerType) => {
// Service filter
if (services.length > 0 && !services.includes(worker.service)) {
return false
}
// Price filter (apply GST calculation here to match frontend)
const priceWithGST = Math.round(worker.pricePerDay * 1.18)
return priceWithGST >= minPrice && priceWithGST <= maxPrice
})

// Sort workers
switch (sortBy) {
case 'price-low':
filteredWorkers.sort((a, b) => a.pricePerDay - b.pricePerDay)
break
case 'price-high':
filteredWorkers.sort((a, b) => b.pricePerDay - a.pricePerDay)
break
case 'name':
default:
filteredWorkers.sort((a, b) => a.name.localeCompare(b.name))
break
}

// Pagination
const startIndex = (page - 1) * limit
const endIndex = startIndex + limit
const paginatedWorkers = filteredWorkers.slice(startIndex, endIndex)

// Calculate total pages
const totalWorkers = filteredWorkers.length
const totalPages = Math.ceil(totalWorkers / limit)

// Get unique services for filter options
const uniqueServices = Array.from(
new Set(workersData.map((worker: WorkerType) => worker.service))
).sort()

// Calculate price range for filters
const allPrices = workersData
.filter((worker: WorkerType) => worker.pricePerDay > 0)
.map((worker: WorkerType) => Math.round(worker.pricePerDay * 1.18))
const priceRange = {
min: Math.min(...allPrices),
max: Math.max(...allPrices)
}

// Response with comprehensive data
const responseData = {
workers: paginatedWorkers,
pagination: {
currentPage: page,
totalPages,
totalWorkers,
limit,
hasNextPage: page < totalPages,
hasPrevPage: page > 1
},
filters: {
services: uniqueServices,
priceRange
}
}

return NextResponse.json(responseData)
} catch (error) {
console.error('API Error:', error)
return NextResponse.json({
success: false,
error: 'Failed to fetch workers data'
}, { status: 500 })
return NextResponse.json(
{ error: 'Failed to fetch workers data' },
{ status: 500 }
)
}
}

43 changes: 43 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
@@ -1 +1,44 @@
@import 'tailwindcss';

/* Custom CSS for improved UI */
@layer utilities {
.line-clamp-1 {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
line-clamp: 1;
}

.line-clamp-2 {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
}
}

/* Smooth scrolling for better UX */
html {
scroll-behavior: smooth;
}

/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}

::-webkit-scrollbar-track {
background: #f1f5f9;
}

::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
19 changes: 16 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import Navbar from "@/components/Navbar";
import ErrorBoundary from "@/components/ErrorBoundary";

const geistSans = Geist({
variable: "--font-geist-sans",
Expand All @@ -13,8 +15,16 @@ const geistMono = Geist_Mono({
});

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "WorkerHub - Find Skilled Workers",
description: "Connect with professional workers for all your service needs. Find the right worker for your project today.",
keywords: "workers, services, professionals, hire, skilled workers",
authors: [{ name: "WorkerHub Team" }],
robots: "index, follow",
};

export const viewport = {
width: 'device-width',
initialScale: 1,
};

export default function RootLayout({
Expand All @@ -27,7 +37,10 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
<ErrorBoundary>
<Navbar />
{children}
</ErrorBoundary>
</body>
</html>
);
Expand Down
Loading