diff --git a/src/app/(dashboard)/wallet/transfer/page.tsx b/src/app/(dashboard)/wallet/transfer/page.tsx
index 8aa1683..fba290d 100644
--- a/src/app/(dashboard)/wallet/transfer/page.tsx
+++ b/src/app/(dashboard)/wallet/transfer/page.tsx
@@ -270,7 +270,7 @@ function WalletTransferContent() {
)}
- }
+ )
}
export default function WalletTransferPage() {
diff --git a/src/app/(dashboard)/wallet/withdraw/steps/otp-step.test.tsx b/src/app/(dashboard)/wallet/withdraw/steps/otp-step.test.tsx
new file mode 100644
index 0000000..ea1adee
--- /dev/null
+++ b/src/app/(dashboard)/wallet/withdraw/steps/otp-step.test.tsx
@@ -0,0 +1,64 @@
+import React from "react"
+import { render, screen, fireEvent } from "@testing-library/react"
+import { describe, it, expect, vi } from "vitest"
+import { OtpStep } from "./otp-step"
+
+describe("OtpStep Component", () => {
+ it("renders OTP input and triggers onResend when Resend button is clicked", () => {
+ const onResend = vi.fn()
+ const onVerify = vi.fn()
+ const onOtpChange = vi.fn()
+
+ render(
+
+ )
+
+ const resendBtn = screen.getByRole("button", { name: /^resend/i })
+ expect(resendBtn).not.toBeDisabled()
+
+ fireEvent.click(resendBtn)
+ expect(onResend).toHaveBeenCalledTimes(1)
+ })
+
+ it("disables Resend button during cooldown and displays countdown", () => {
+ render(
+
+ )
+
+ const resendBtn = screen.getByRole("button", { name: /resend in 45s/i })
+ expect(resendBtn).toBeDisabled()
+ })
+
+ it("disables Resend button during active loading state", () => {
+ render(
+
+ )
+
+ const resendBtn = screen.getByRole("button", { name: /^resend/i })
+ expect(resendBtn).toBeDisabled()
+ })
+})
diff --git a/src/app/api/claim-name/route.test.ts b/src/app/api/claim-name/route.test.ts
new file mode 100644
index 0000000..f3fa9c9
--- /dev/null
+++ b/src/app/api/claim-name/route.test.ts
@@ -0,0 +1,113 @@
+import { describe, it, expect, beforeEach } from "vitest";
+import { NextRequest } from "next/server";
+import { POST, _resetClaimNameState } from "./route";
+
+describe("POST /api/claim-name", () => {
+ beforeEach(() => {
+ _resetClaimNameState();
+ });
+
+ it("claims a valid name for an authenticated user", async () => {
+ const req = new NextRequest("http://localhost/api/claim-name", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: "Bearer user-100",
+ },
+ body: JSON.stringify({ name: " Satoshi_N " }),
+ });
+
+ const res = await POST(req);
+ expect(res.status).toBe(201);
+
+ const data = await res.json();
+ expect(data.success).toBe(true);
+ expect(data.name).toBe("satoshi_n");
+ expect(data.claimedBy).toBe("user-100");
+ });
+
+ it("rejects unauthenticated requests", async () => {
+ const req = new NextRequest("http://localhost/api/claim-name", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ name: "valid_name" }),
+ });
+
+ const res = await POST(req);
+ expect(res.status).toBe(401);
+
+ const data = await res.json();
+ expect(data.error).toMatch(/Authentication required/i);
+ });
+
+ it("rejects invalid or malformed names", async () => {
+ // Too short (< 3 chars)
+ const reqShort = new NextRequest("http://localhost/api/claim-name", {
+ method: "POST",
+ headers: { Authorization: "Bearer user-100" },
+ body: JSON.stringify({ name: "ab" }),
+ });
+ const resShort = await POST(reqShort);
+ expect(resShort.status).toBe(400);
+
+ // Illegal characters
+ const reqIllegal = new NextRequest("http://localhost/api/claim-name", {
+ method: "POST",
+ headers: { Authorization: "Bearer user-100" },
+ body: JSON.stringify({ name: "satoshi