From 5c35ea46f867f2be2ba90a4484f532d4f79c22d2 Mon Sep 17 00:00:00 2001
From: Sonu Anand <115688907+Sonuanand07@users.noreply.github.com>
Date: Thu, 25 Sep 2025 23:59:39 +0530
Subject: [PATCH] Assignment: all required features and improvements
---
README.md | 29 ++++-
components/Filters.tsx | 26 ++++
components/Navbar.tsx | 17 +++
components/Pagination.tsx | 22 ++++
components/SkeltonGrid.tsx | 17 +++
components/WorkerCard.tsx | 27 ++++
jest.config.js | 7 +
src/_tests_/WorkerCard.tsx | 20 +++
src/app/Navbar.tsx | 16 +++
src/app/api/workers/route.ts | 24 ++--
src/app/layout.tsx | 2 +
src/app/page.tsx | 243 ++++++++++++++++++++++++++++-------
12 files changed, 390 insertions(+), 60 deletions(-)
create mode 100644 components/Filters.tsx
create mode 100644 components/Navbar.tsx
create mode 100644 components/Pagination.tsx
create mode 100644 components/SkeltonGrid.tsx
create mode 100644 components/WorkerCard.tsx
create mode 100644 jest.config.js
create mode 100644 src/_tests_/WorkerCard.tsx
create mode 100644 src/app/Navbar.tsx
diff --git a/README.md b/README.md
index 4389483..fc130a0 100644
--- a/README.md
+++ b/README.md
@@ -84,4 +84,31 @@ This assignment is designed to assess your practical skills in **React, Next.js,
- Add comment for any **bug fix or optimization.**
- Document any **extra improvements** you make in your submission.
-Good luck 🚀
+## How to run locally
+
+```bash
+npm install
+npm run dev
+```
+Node version: 18.x+
+
+## What I implemented
+
+- Responsive card grid and improved card UI
+- Sticky, responsive navbar
+- Pagination and filters for workers
+- API integration with loading/error states
+- Performance optimizations (lazy loading, memoization)
+- Unit/component tests
+
+## Trade-offs / Known Issues
+
+- [List any limitations or decisions you made]
+
+## How to run tests
+
+```bash
+npm test
+```
+
+Good luck 🚀
diff --git a/components/Filters.tsx b/components/Filters.tsx
new file mode 100644
index 0000000..397bd26
--- /dev/null
+++ b/components/Filters.tsx
@@ -0,0 +1,26 @@
+// components/Filters.tsx
+import React from 'react';
+
+export default function Filters({ onTypeChange, onPriceChange }:{ onTypeChange:(v:string|null)=>void, onPriceChange:(r:[number,number]|null)=>void }) {
+ return (
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/components/Navbar.tsx b/components/Navbar.tsx
new file mode 100644
index 0000000..1fcee89
--- /dev/null
+++ b/components/Navbar.tsx
@@ -0,0 +1,17 @@
+// components/Navbar.tsx
+import Link from 'next/link';
+
+export default function Navbar() {
+ return (
+
+
+ SolveEase
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/components/Pagination.tsx b/components/Pagination.tsx
new file mode 100644
index 0000000..b8dc781
--- /dev/null
+++ b/components/Pagination.tsx
@@ -0,0 +1,22 @@
+// components/Pagination.tsx
+import React from 'react';
+
+export default function Pagination({ current, total, onChange } : { current:number, total:number, onChange:(n:number)=>void }) {
+ const pages = Array.from({length: total}, (_,i)=>i+1);
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/components/SkeltonGrid.tsx b/components/SkeltonGrid.tsx
new file mode 100644
index 0000000..d9e4fc0
--- /dev/null
+++ b/components/SkeltonGrid.tsx
@@ -0,0 +1,17 @@
+// components/SkeletonGrid.tsx
+import React from 'react';
+
+export default function SkeletonGrid({ cols=3, rows=3 }:{cols?:number, rows?:number}) {
+ const items = Array.from({length: cols*rows});
+ return (
+
+ {items.map((_,i) => (
+
+
+
+
+
+ ))}
+
+ );
+}
\ No newline at end of file
diff --git a/components/WorkerCard.tsx b/components/WorkerCard.tsx
new file mode 100644
index 0000000..1458e34
--- /dev/null
+++ b/components/WorkerCard.tsx
@@ -0,0 +1,27 @@
+// components/WorkerCard.tsx
+import React from 'react';
+import Image from 'next/image';
+
+export default function WorkerCard({ worker }:{ worker:any }) {
+ return (
+
+
+
+
+
+
{worker.name}
+
{worker.description}
+
+ ₹{worker.price}/day
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 0000000..74ca942
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,7 @@
+module.exports = {
+ testEnvironment: 'jsdom',
+ transform: { '^.+\\.[tj]sx?$': 'ts-jest' },
+ moduleNameMapper: {
+ '\\.(css|scss)$': 'identity-obj-proxy',
+ },
+};
\ No newline at end of file
diff --git a/src/_tests_/WorkerCard.tsx b/src/_tests_/WorkerCard.tsx
new file mode 100644
index 0000000..25f125d
--- /dev/null
+++ b/src/_tests_/WorkerCard.tsx
@@ -0,0 +1,20 @@
+import { render, screen } from "@testing-library/react";
+import WorkerCard from "../app/page"; // adjust import if WorkerCard is in a separate file
+
+const worker = {
+ id: 1,
+ name: "John Doe",
+ service: "Plumber",
+ pricePerDay: 500,
+ image: "/john.jpg",
+};
+
+describe("WorkerCard", () => {
+ it("renders worker details", () => {
+ render();
+ expect(screen.getByText("John Doe")).toBeInTheDocument();
+ expect(screen.getByText("Plumber")).toBeInTheDocument();
+ expect(screen.getByText("₹500/day")).toBeInTheDocument();
+ expect(screen.getByAltText("John Doe")).toBeInTheDocument();
+ });
+});
\ No newline at end of file
diff --git a/src/app/Navbar.tsx b/src/app/Navbar.tsx
new file mode 100644
index 0000000..620d87e
--- /dev/null
+++ b/src/app/Navbar.tsx
@@ -0,0 +1,16 @@
+import React from "react";
+import Link from "next/link";
+
+export default function Navbar() {
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/api/workers/route.ts b/src/app/api/workers/route.ts
index 44a245e..de5d866 100644
--- a/src/app/api/workers/route.ts
+++ b/src/app/api/workers/route.ts
@@ -1,18 +1,18 @@
-import { NextResponse } from 'next/server'
-import workersData from '../../../../workers.json'
+import { NextResponse } from 'next/server';
+import fs from 'fs';
+import path from 'path';
export async function GET() {
try {
- return NextResponse.json({
- success: true,
- data: workersData
- })
- } catch (error) {
- console.error('API Error:', error)
- return NextResponse.json({
- success: false,
- error: 'Failed to fetch workers data'
- }, { status: 500 })
+ // adjust path if your workers.json lives somewhere else
+ const filePath = path.join(process.cwd(), 'data', 'workers.json');
+ const raw = fs.readFileSync(filePath, 'utf8');
+ const data = JSON.parse(raw);
+ // optionally: support query params for paging/filters here
+ return NextResponse.json({ ok: true, data });
+ } catch (err) {
+ console.error('API /api/wprkers error:', err);
+ return NextResponse.json({ ok: false, error: 'Failed to load workers' }, { status: 500 });
}
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index f7fa87e..b32ff94 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
+import Navbar from "./Navbar";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -27,6 +28,7 @@ export default function RootLayout({
+
{children}