diff --git a/package-lock.json b/package-lock.json index 3fdb753..693fb7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend_dev_assignment", "version": "0.1.0", "dependencies": { + "lucide-react": "^0.544.0", "next": "15.5.4", "react": "19.1.0", "react-dom": "19.1.0" @@ -4453,6 +4454,15 @@ "loose-envify": "cli.js" } }, + "node_modules/lucide-react": { + "version": "0.544.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.544.0.tgz", + "integrity": "sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.19", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", diff --git a/package.json b/package.json index 252da23..7843e0e 100644 --- a/package.json +++ b/package.json @@ -9,19 +9,20 @@ "lint": "eslint" }, "dependencies": { + "lucide-react": "^0.544.0", + "next": "15.5.4", "react": "19.1.0", - "react-dom": "19.1.0", - "next": "15.5.4" + "react-dom": "19.1.0" }, "devDependencies": { - "typescript": "^5", + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "@tailwindcss/postcss": "^4", - "tailwindcss": "^4", "eslint": "^9", "eslint-config-next": "15.5.4", - "@eslint/eslintrc": "^3" + "tailwindcss": "^4", + "typescript": "^5" } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f7fa87e..9386ec6 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -25,7 +25,7 @@ export default function RootLayout({ return ( {children} diff --git a/src/app/page.tsx b/src/app/page.tsx index 23eaf49..429340f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,57 +1,69 @@ 'use client' +import { ImageContainer } from '@/components/images-container' +import {Navbar} from '@/components/navbar' import { WorkerType } from '@/types/workers' import Image from 'next/image' import { useState, useEffect } from 'react' -export default function WorkersPage() { - const [workersData, setWorkersData] = useState([]) +// export default function WorkersPage() { +// const [workersData, setWorkersData] = useState([]) - useEffect(() => { - const loadData = async () => { - try { - const response = await import('../../workers.json') - setWorkersData(response.default) - } catch (error) { - console.error('Failed to load workers:', error) - } - } - loadData() - loadData() - }, []) +// const loadData = async () => { +// try { +// const response = await fetch('/api/workers') +// const data = await response.json() +// setWorkersData(data.data) +// } catch (error) { +// console.error('Failed to load workers:', error) +// } +// } - return ( -
-

Our Workers

+// useEffect(() => { +// loadData() +// }, []) -
- {workersData - .filter((worker) => worker.pricePerDay > 0) - .filter((worker) => worker.id !== null) - .sort((a, b) => a.name.localeCompare(b.name)) - .map((worker: WorkerType) => ( -
-
- {worker.name} -
-
-

{worker.name}

-

{worker.service}

-

- ₹{Math.round(worker.pricePerDay * 1.18)} / day -

-
-
- ))} -
-
- ) -} +// return ( +//
+//

Our Workers

+ +//
+// {workersData +// .filter((worker) => worker.pricePerDay > 0) +// .filter((worker) => worker.id !== null) +// .sort((a, b) => a.name.localeCompare(b.name)) +// .map((worker: WorkerType) => ( +//
+//
+// {worker.name} +//
+//
+//

{worker.name}

+//

{worker.service}

+//

+// ₹{Math.round(worker.pricePerDay * 1.18)} / day +//

+//
+//
+// ))} +//
+//
+// ) +// } + +export default function WorkersPage(){ +return ( + <> + + + +) +} \ No newline at end of file diff --git a/src/components/image-card.tsx b/src/components/image-card.tsx new file mode 100644 index 0000000..10ffcba --- /dev/null +++ b/src/components/image-card.tsx @@ -0,0 +1,32 @@ +import Image from "next/image" +import { WorkerType } from "@/types/workers" + +export function ImageCard({ id, name, service, image, pricePerDay }: WorkerType) { + return ( +
+
+ {name} +
+ +
+

{name}

+

{service}

+
+ ₹{pricePerDay}/day + +
+
+
+ ) +} diff --git a/src/components/images-container.tsx b/src/components/images-container.tsx new file mode 100644 index 0000000..7273166 --- /dev/null +++ b/src/components/images-container.tsx @@ -0,0 +1,129 @@ +"use client" + +import { useEffect, useState, useMemo } from "react" +import { ImageCard } from "./image-card" +import { useWorkers } from "@/hooks/useWorkers" +import { WorkerType } from "@/types/workers" +import { ChevronLeft, ChevronRight } from "lucide-react" + +function SkeletonCard() { + return ( +
+
+
+
+
+
+
+
+ ) +} + +export function ImageContainer() { + const { workers, loading } = useWorkers() + const [counter, setCounter] = useState(0) + const [priceRange, setPriceRange] = useState<[number, number]>([0, 10000]) + const [serviceType, setServiceType] = useState("all") + + const services = useMemo(() => { + const unique = Array.from(new Set(workers.map((w) => w.service))) + return ["all", ...unique] + }, [workers]) + + const filteredWorkers = useMemo(() => { + return workers.filter((w) => { + const inPriceRange = w.pricePerDay >= priceRange[0] && w.pricePerDay <= priceRange[1] + const matchesService = serviceType === "all" || w.service.toLowerCase() === serviceType.toLowerCase() + return inPriceRange && matchesService + }) + }, [workers, priceRange, serviceType]) + + const sliceWorkers = useMemo(() => { + return filteredWorkers.slice(counter, counter + 12) + }, [filteredWorkers, counter]) + + useEffect(() => { + setCounter(0) + }, [priceRange, serviceType]) + + const handleNext = () => { + setCounter((prev) => (prev + 12 >= filteredWorkers.length ? 0 : prev + 12)) + } + + const handlePrev = () => { + setCounter((prev) => (prev - 12 < 0 ? Math.max(filteredWorkers.length - 12, 0) : prev - 12)) + } + + if (loading) { + return ( +
+ {Array.from({ length: 12 }).map((_, i) => ( + + ))} +
+ ) + } + + return ( +
+
+ + +
+ setPriceRange([+e.target.value || 0, priceRange[1]])} + /> + - + setPriceRange([priceRange[0], +e.target.value || 10000])} + /> +
+
+ + {sliceWorkers.length > 0 ? ( + <> +
+ {sliceWorkers.map((worker) => ( + + ))} +
+ +
+ + +
+ + ) : ( +

No workers found.

+ )} +
+ ) +} diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx new file mode 100644 index 0000000..5038d2c --- /dev/null +++ b/src/components/navbar.tsx @@ -0,0 +1,62 @@ +"use client" + +import { useState } from "react" +import Link from "next/link" +import { Menu, X } from "lucide-react" + +export function Navbar() { + const [isOpen, setIsOpen] = useState(false) + + return ( + + ) +} diff --git a/src/hooks/useWorkers.tsx b/src/hooks/useWorkers.tsx new file mode 100644 index 0000000..44f3e0c --- /dev/null +++ b/src/hooks/useWorkers.tsx @@ -0,0 +1,30 @@ +"use client" +import { useState, useEffect } from "react" +import { WorkerType } from "@/types/workers" + +export function useWorkers() { + + const [workers, setWorkers] = useState([]) + const [loading, setLoading] = useState(false); + + const fetchWorkers = async () => { + try { + setLoading(true) + const response = await fetch('/api/workers') + const data = await response.json(); + setWorkers(data.data) + } catch (e) { + console.log(e) + } + finally{ + setLoading(false); + } + } + + useEffect(() => { + fetchWorkers() + }, []) + + + return { workers, setWorkers, loading, setLoading } +} \ No newline at end of file