diff --git a/myhaki/package.json b/myhaki/package.json index a689088..6fa7a2d 100644 --- a/myhaki/package.json +++ b/myhaki/package.json @@ -8,6 +8,7 @@ "start": "next start", "lint": "eslint", "test": "jest" + }, "dependencies": { "@heroicons/react": "^2.2.0", diff --git a/myhaki/src/app/api/login/route.ts b/myhaki/src/app/api/login/route.ts index 87fc809..43e03d5 100644 --- a/myhaki/src/app/api/login/route.ts +++ b/myhaki/src/app/api/login/route.ts @@ -1,4 +1,4 @@ -import { NextResponse } from "next/server"; +import { error } from "console"; const baseUrl = process.env.BASE_URL; @@ -6,27 +6,25 @@ export async function POST(request: Request) { try { const { email, password } = await request.json(); - if (!email || !password) { - return NextResponse.json({ detail: "Email and password are required." }, { status: 400 }); - } - - if (!baseUrl) { - return NextResponse.json({ detail: "BASE_URL environment variable not set" }, { status: 500 }); - } - - const response = await fetch(`${baseUrl}/api/login/`, { - method: "POST", - headers: { "Content-Type": "application/json" }, + const response = await fetch(`${baseUrl}/login/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }); const result = await response.json(); - return NextResponse.json(result, { status: response.status }); + if (!response.ok) { + return new Response(JSON.stringify(result), { status: response.status }); + } + + return new Response(JSON.stringify(result), { status: 200 }); } catch (error) { - return NextResponse.json( - { detail: (error as Error).message || "Internal Server Error" }, - { status: 500 } - ); + return new Response(JSON.stringify({ error: (error as Error).message }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); } } + + diff --git a/myhaki/src/app/api/users/[id]/route.ts b/myhaki/src/app/api/users/[id]/route.ts new file mode 100644 index 0000000..9e629c0 --- /dev/null +++ b/myhaki/src/app/api/users/[id]/route.ts @@ -0,0 +1,70 @@ +import { NextRequest } from 'next/server'; + +const baseUrl = process.env.BASE_URL; + +export async function GET(request: NextRequest) { + const id = request.nextUrl.pathname.split('/').filter(Boolean).pop(); + + if (!id) { + return new Response(JSON.stringify({ error: 'User ID is required' }), { + status: 400, + headers: { 'Content-Type': 'application/json' }, + }); + } + + try { + const token = request.headers.get('authorization'); + const headers: HeadersInit = {}; + if (token) headers['Authorization'] = token; + + const response = await fetch(`${baseUrl}/users/${id}/`, { headers }); + const result = await response.json(); + return new Response(JSON.stringify(result), { + status: response.status, + headers: { 'Content-Type': 'application/json' }, + }); + } catch (error) { + return new Response(JSON.stringify({ error: (error as Error).message }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +} + +export async function PATCH(request: NextRequest) { + + const id = request.nextUrl.pathname.split('/').filter(Boolean).pop(); + + if (!id) { + return new Response(JSON.stringify({ error: 'User ID is required' }), { + status: 400, + headers: { 'Content-Type': 'application/json' }, + }); + } + + try { + const body = await request.json(); + const token = request.headers.get('authorization'); + const headers: HeadersInit = { + 'Content-Type': 'application/json', + }; + if (token) headers['Authorization'] = token; + + const response = await fetch(`${baseUrl}/users/${id}/`, { + method: 'PATCH', + headers, + body: JSON.stringify(body), + }); + + const result = await response.json(); + return new Response(JSON.stringify(result), { + status: response.status, + headers: { 'Content-Type': 'application/json' }, + }); + }catch (error) { + return new Response(JSON.stringify({ error: (error as Error).message }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +} \ No newline at end of file diff --git a/myhaki/src/app/api/users/route.ts b/myhaki/src/app/api/users/route.ts deleted file mode 100644 index 8671924..0000000 --- a/myhaki/src/app/api/users/route.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; - - -const baseUrl = process.env.BASE_URL; -const authToken = process.env.AUTH_TOKEN; - - -export async function GET(request: NextRequest) { - try { - if (!baseUrl) return NextResponse.json({ detail: "BASE_URL not set" }, { status: 500 }); - - - const { pathname } = request.nextUrl; - const pathSegments = pathname.split("/"); - const userId = pathSegments[pathSegments.length - 1]; - if (!userId) return NextResponse.json({ detail: "User ID not provided" }, { status: 400 }); - - - const token = request.headers.get("authorization") || `Token ${authToken || ""}`; - const url = `${baseUrl}/api/users/${userId}/`; - const response = await fetch(url, { - method: "GET", - headers: { "Content-Type": "application/json", ...(token ? { Authorization: token } : {}) }, - }); - - - if (!response.ok) { - const errText = await response.text(); - return NextResponse.json({ detail: errText || "Error fetching user" }, { status: response.status }); - } - - - const data = await response.json(); - return NextResponse.json(data, { status: 200 }); - } catch (error) { - return NextResponse.json({ detail: (error as Error).message || "Internal Server Error" }, { status: 500 }); - } -} - - -export async function PUT(request: NextRequest) { - try { - if (!baseUrl) return NextResponse.json({ detail: "BASE_URL not set" }, { status: 500 }); - - - const { pathname } = request.nextUrl; - const pathSegments = pathname.split("/"); - const userId = pathSegments[pathSegments.length - 1]; - if (!userId) return NextResponse.json({ detail: "User ID not provided" }, { status: 400 }); - - - const token = request.headers.get("authorization") || `Token ${authToken || ""}`; - const url = `${baseUrl}/api/users/${userId}/`; - - - const formData = await request.formData(); - - - const response = await fetch(url, { - method: "PUT", - headers: { ...(token ? { Authorization: token } : {}) }, - body: formData, - }); - - - if (!response.ok) { - const errText = await response.text(); - return NextResponse.json({ detail: errText || "Failed to update profile" }, { status: response.status }); - } - - - const data = await response.json(); - return NextResponse.json(data, { status: 200 }); - } catch (error) { - return NextResponse.json({ detail: (error as Error).message || "Internal Server Error" }, { status: 500 }); - } -} - - diff --git a/myhaki/src/app/authentication/sign-in/page.test.tsx b/myhaki/src/app/authentication/sign-in/page.test.tsx index 3c8e095..ff10057 100644 --- a/myhaki/src/app/authentication/sign-in/page.test.tsx +++ b/myhaki/src/app/authentication/sign-in/page.test.tsx @@ -1,33 +1,42 @@ import React from "react"; -import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import { render, screen, fireEvent, waitFor, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import SignInPage from "./page"; -jest.mock("next/image", () => (props: any) => {props.alt}); +jest.mock("next/image", () => ({ + __esModule: true, + default: (props: React.ImgHTMLAttributes) => , +})); + +jest.mock('@/app/hooks/useFetchSignIn', () => { + const React = require('react'); + return { + __esModule: true, + default: jest.fn(() => { + const [email, setEmail] = React.useState(''); + const [password, setPassword] = React.useState(''); + const [error, setError] = React.useState(''); + const [message, setMessage] = React.useState(''); + const signin = jest.fn(); + return { email, setEmail, password, setPassword, error, message, signin }; + }), + }; +}); -jest.mock('@/app/hooks/useFetchSignIn', () => ({ - useSignIn: jest.fn(), +jest.mock('next/navigation', () => ({ + useRouter: () => ({ + push: jest.fn(), + replace: jest.fn(), + prefetch: jest.fn(() => Promise.resolve()), + pathname: '/sign-in', + }), + usePathname: () => '/sign-in', + useSearchParams: () => new URLSearchParams(), })); -import { useSignIn } from '@/app/hooks/useFetchSignIn'; +import useFetchSignin from '@/app/hooks/useFetchSignIn'; describe("SignInPage", () => { - const mockHandleSignIn = jest.fn(); - const setEmail = jest.fn(); - const setPassword = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - (useSignIn as jest.Mock).mockReturnValue({ - email: "", - setEmail, - password: "", - setPassword, - error: "", - message: "", - handleSignIn: mockHandleSignIn, - }); - }); - it("renders form inputs and buttons", () => { render(); expect(screen.getByLabelText(/email/i)).toBeInTheDocument(); @@ -36,59 +45,71 @@ describe("SignInPage", () => { expect(screen.getByRole("button", { name: /show password/i })).toBeInTheDocument(); }); - it("toggles password visibility on button click", () => { + it("toggles password visibility on button click", async () => { render(); const passwordInput = screen.getByLabelText(/^password$/i); const toggleButton = screen.getByRole("button", { name: /show password/i }); expect(passwordInput).toHaveAttribute("type", "password"); - fireEvent.click(toggleButton); + await userEvent.click(toggleButton); expect(toggleButton).toHaveAttribute("aria-label", "Hide password"); expect(passwordInput).toHaveAttribute("type", "text"); - fireEvent.click(toggleButton); + await userEvent.click(toggleButton); expect(toggleButton).toHaveAttribute("aria-label", "Show password"); expect(passwordInput).toHaveAttribute("type", "password"); }); - it("calls setEmail and setPassword on input changes", () => { + it("calls setEmail and setPassword on input changes", async () => { render(); - fireEvent.change(screen.getByLabelText(/email/i), { target: { value: "user@test.com" } }); - expect(setEmail).toHaveBeenCalledWith("user@test.com"); + const emailInput = screen.getByLabelText(/email/i); + await userEvent.type(emailInput, "user@test.com"); + expect(emailInput).toHaveValue("user@test.com"); - fireEvent.change(screen.getByLabelText(/^password$/i), { target: { value: "mypassword" } }); - expect(setPassword).toHaveBeenCalledWith("mypassword"); + const passwordInput = screen.getByLabelText(/^password$/i); + await userEvent.type(passwordInput, "mypassword"); + expect(passwordInput).toHaveValue("mypassword"); }); - it("calls handleSignIn on form submission", async () => { - mockHandleSignIn.mockResolvedValueOnce(undefined); + it("calls signin on form submission", async () => { + const signinMock = jest.fn(); + (useFetchSignin as jest.Mock).mockReturnValue({ + email: '', + setEmail: jest.fn(), + password: '', + setPassword: jest.fn(), + error: '', + message: '', + signin: signinMock, + }); const { container } = render(); - const form = container.querySelector("form"); - if (!form) throw new Error("Form element not found"); + const form = container.querySelector('form'); + if (!form) throw new Error('Form element not found'); fireEvent.submit(form); await waitFor(() => { - expect(mockHandleSignIn).toHaveBeenCalled(); + expect(signinMock).toHaveBeenCalled(); }); }); it("shows error and message when provided", () => { - (useSignIn as jest.Mock).mockReturnValue({ - email: "", + (useFetchSignin as jest.Mock).mockReturnValue({ + email: '', setEmail: jest.fn(), - password: "", + password: '', setPassword: jest.fn(), - error: "Invalid credentials", - message: "Welcome back!", - handleSignIn: mockHandleSignIn, + error: 'Invalid credentials', + signin: jest.fn(), }); render(); - expect(screen.getByText("Invalid credentials")).toBeInTheDocument(); - expect(screen.getByText("Welcome back!")).toBeInTheDocument(); + expect(screen.getByText('Invalid credentials')).toBeInTheDocument(); + + const container = screen.getByText('Invalid credentials').parentElement; + expect(container).not.toBeNull(); }); }); diff --git a/myhaki/src/app/authentication/sign-in/page.tsx b/myhaki/src/app/authentication/sign-in/page.tsx index 39f63aa..a82ddf7 100644 --- a/myhaki/src/app/authentication/sign-in/page.tsx +++ b/myhaki/src/app/authentication/sign-in/page.tsx @@ -1,21 +1,28 @@ -"use client"; - +'use client'; import Image from "next/image"; import { useState } from "react"; -import { useSignIn } from "@/app/hooks/useFetchSignIn"; - +import { useRouter } from "next/navigation"; +import useFetchSignin from "@/app/hooks/useFetchSignIn"; export default function SignInPage() { - const { email, setEmail, password, setPassword, error, message, handleSignIn } = useSignIn(); - const [showPassword, setShowPassword] = useState(false); - - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - await handleSignIn(); - }; - + const { signin, error, loading } = useFetchSignin(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + const [message, setMessage] = useState(null); + const router = useRouter(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + try { + await signin(email, password); + setMessage("Sign in successful"); + router.push("/dashboard"); + } catch { + setMessage(null); + } + }; return (
@@ -32,106 +39,73 @@ export default function SignInPage() {
-
-

Sign In

-
-
- - setEmail(e.target.value)} - required - autoComplete="email" - /> -
- - -
- - setPassword(e.target.value)} - required - autoComplete="current-password" - minLength={6} - /> - -
- - - - +
+

Sign In

+ +
+ + setEmail(e.target.value)} + required + autoComplete="email" + /> +
+ +
+ + setPassword(e.target.value)} + required + autoComplete="current-password" + minLength={6} + /> + +
+ + {error &&
{error}
} {message &&
{message}
} - - -
-
- ); + + + + + ); } diff --git a/myhaki/src/app/authentication/verify-otp/page.test.tsx b/myhaki/src/app/authentication/verify-otp/page.test.tsx index ce2943f..8434fcc 100644 --- a/myhaki/src/app/authentication/verify-otp/page.test.tsx +++ b/myhaki/src/app/authentication/verify-otp/page.test.tsx @@ -19,10 +19,8 @@ describe("VerifyOtpPage", () => { beforeEach(() => { jest.clearAllMocks(); - // Mock router (useRouter as jest.Mock).mockReturnValue({ push: pushMock }); - // Mock hooks (useVerifyOtp as jest.Mock).mockReturnValue({ otp: "", setOtp: setOtpMock, diff --git a/myhaki/src/app/cases/components/Case-Detail-Modal/index.tsx b/myhaki/src/app/cases/components/Case-Detail-Modal/index.tsx index 959e477..bf4e8cb 100644 --- a/myhaki/src/app/cases/components/Case-Detail-Modal/index.tsx +++ b/myhaki/src/app/cases/components/Case-Detail-Modal/index.tsx @@ -31,7 +31,7 @@ export default function CaseDetailModal({ caseItem, onClose }: CaseDetailModalPr return (
-
+

Case Details - Case ID: {caseItem.case_id}

diff --git a/myhaki/src/app/cases/components/Case-Status-Bar/index.tsx b/myhaki/src/app/cases/components/Case-Status-Bar/index.tsx index 7c7ece6..4a14f7a 100644 --- a/myhaki/src/app/cases/components/Case-Status-Bar/index.tsx +++ b/myhaki/src/app/cases/components/Case-Status-Bar/index.tsx @@ -1,25 +1,30 @@ 'use client'; + import Image from "next/image"; import CaseTable from '../Cases-Table'; -import useFetchCases from '@/app/hooks/useFetchCases'; +import useFetchCases from '@/app/hooks/useFetchCases'; + export default function CaseStatusBar() { - const { cases, loading } = useFetchCases(); + const { cases, loading } = useFetchCases(); const totalCases = cases.length; - return ( -
+ + return ( +

Case overview

Case Overview Banner
-