diff --git a/next.config.ts b/next.config.ts index 156ac95..900be79 100644 --- a/next.config.ts +++ b/next.config.ts @@ -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; diff --git a/src/app/api/workers/route.ts b/src/app/api/workers/route.ts index 44a245e..593b3d8 100644 --- a/src/app/api/workers/route.ts +++ b/src/app/api/workers/route.ts @@ -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 } + ) } } diff --git a/src/app/globals.css b/src/app/globals.css index d4b5078..fffab31 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -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; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f7fa87e..61921c2 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -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", @@ -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({ @@ -27,7 +37,10 @@ export default function RootLayout({
- {children} +