- {errors.map((err, index) => (
+ {messages.map((err, index) => (
{err}
diff --git a/src/lib/api.ts b/src/lib/api.ts
index 3fc3b656..a20a6db2 100644
--- a/src/lib/api.ts
+++ b/src/lib/api.ts
@@ -187,6 +187,7 @@ class ApiClientImpl {
body?.message || response.statusText,
statusToUserMessage(response.status),
response.status,
+ body?.errors,
);
}
diff --git a/src/lib/validation.ts b/src/lib/validation.ts
index ee970fe1..f449bcc6 100644
--- a/src/lib/validation.ts
+++ b/src/lib/validation.ts
@@ -1,10 +1,15 @@
import { NextResponse } from 'next/server';
-import { ZodTypeAny, ZodError, z } from 'zod';
+import { ZodTypeAny, z } from 'zod';
// ---------------------------------------------------------------------------
// Discriminated union result type — TypeScript narrows correctly on `.ok`
// ---------------------------------------------------------------------------
+export type ValidationFieldError = {
+ field: string;
+ message: string;
+};
+
type ValidationSuccess = { ok: true; data: T };
type ValidationFailure = { ok: false; error: NextResponse };
export type ValidationResult = ValidationSuccess | ValidationFailure;
@@ -19,10 +24,14 @@ export function validateBody(
): ValidationResult> {
const result = schema.safeParse(input);
if (!result.success) {
+ const errors: ValidationFieldError[] = result.error.issues.map((issue) => ({
+ field: issue.path.join('.'),
+ message: issue.message,
+ }));
return {
ok: false,
error: NextResponse.json(
- { success: false, message: formatZodError(result.error) },
+ { message: 'Validation failed', errors },
{ status: 400 },
),
};
@@ -41,11 +50,3 @@ export function validateQuery(
const raw = Object.fromEntries(searchParams.entries());
return validateBody(schema, raw);
}
-
-// ---------------------------------------------------------------------------
-// Helpers
-// ---------------------------------------------------------------------------
-
-function formatZodError(error: ZodError): string {
- return error.errors.map((e) => e.message).join('; ');
-}
diff --git a/src/utils/error-handler.ts b/src/utils/error-handler.ts
index edb31476..0d493524 100644
--- a/src/utils/error-handler.ts
+++ b/src/utils/error-handler.ts
@@ -38,6 +38,7 @@ export function parseApiError(error: unknown): ErrorInfo {
timestamp: now,
retryable: error.statusCode ? error.statusCode >= 500 : true,
userMessage: error.userMessage,
+ details: error.errors ? { validationErrors: error.errors } : undefined,
};
}
@@ -51,12 +52,18 @@ export function parseApiError(error: unknown): ErrorInfo {
};
}
+export type ApiFieldError = {
+ field: string;
+ message: string;
+};
+
export class ApiError extends Error {
constructor(
public readonly type: ErrorType,
message: string,
public readonly userMessage: string,
public readonly statusCode?: number,
+ public readonly errors?: ApiFieldError[],
) {
super(message);
this.name = 'ApiError';
From a06d604bc6a3a2aea4c2e83accc41db511ff0993 Mon Sep 17 00:00:00 2001
From: Caesarr
Date: Sun, 28 Jun 2026 11:48:00 +0100
Subject: [PATCH 2/5] updated
---
src/components/forms/FormError.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/forms/FormError.tsx b/src/components/forms/FormError.tsx
index c074a5f0..28ca3c1c 100644
--- a/src/components/forms/FormError.tsx
+++ b/src/components/forms/FormError.tsx
@@ -19,7 +19,7 @@ interface FormErrorProps {
}
function isStructuredErrors(value: FormErrorValue): value is ApiFieldError[] {
- return Array.isArray(value) && value.length > 0 && 'field' in value[0];
+ return Array.isArray(value) && value.length > 0 && typeof value[0] === 'object' && value[0] !== null && 'field' in value[0];
}
export function FormError({ error, className = '', id }: FormErrorProps) {
From 8169ccfd31afa1527071da8d80aff1641f57d7fe Mon Sep 17 00:00:00 2001
From: Caesarr
Date: Sun, 28 Jun 2026 11:56:47 +0100
Subject: [PATCH 3/5] Updated
---
src/components/forms/FormError.tsx | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/components/forms/FormError.tsx b/src/components/forms/FormError.tsx
index 28ca3c1c..55061ddf 100644
--- a/src/components/forms/FormError.tsx
+++ b/src/components/forms/FormError.tsx
@@ -19,7 +19,10 @@ interface FormErrorProps {
}
function isStructuredErrors(value: FormErrorValue): value is ApiFieldError[] {
- return Array.isArray(value) && value.length > 0 && typeof value[0] === 'object' && value[0] !== null && 'field' in value[0];
+ if (!Array.isArray(value) || value.length === 0) return false;
+ const first = value[0];
+ if (typeof first !== 'object' || first === null) return false;
+ return 'field' in first;
}
export function FormError({ error, className = '', id }: FormErrorProps) {
From 0137c94448b905cca977194dafbda613a4254818 Mon Sep 17 00:00:00 2001
From: Caesarr
Date: Thu, 9 Jul 2026 18:37:06 +0100
Subject: [PATCH 4/5] fix(pdf): correct waitUntil value and
Uint8Array-to-Buffer type mismatch
---
src/services/pdf-generation.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/services/pdf-generation.ts b/src/services/pdf-generation.ts
index aa4020c2..ba603db5 100644
--- a/src/services/pdf-generation.ts
+++ b/src/services/pdf-generation.ts
@@ -27,12 +27,12 @@ export async function generatePDF(
// Apply the required default timeout (25 000 ms) to prevent indefinite hangs.
page.setDefaultTimeout(25000);
- // Load the HTML content. "networkidle0" waits for all network requests to finish.
- await page.setContent(html, { waitUntil: 'networkidle0' });
+ // Load the HTML content. Use "domcontentloaded" instead of "networkidle0"
+ await page.setContent(html, { waitUntil: 'domcontentloaded' });
// Generate the PDF. Caller may supply additional options.
const pdfBuffer = await page.pdf({ format: 'A4', ...options });
- return pdfBuffer;
+ return Buffer.isBuffer(pdfBuffer) ? pdfBuffer : Buffer.from(pdfBuffer);
} finally {
// Ensure the browser process is always cleaned up, even on errors or timeouts.
await browser.close();
From d6bac576fe06625e714aec36ff1685195b045515 Mon Sep 17 00:00:00 2001
From: Caesarr
Date: Thu, 9 Jul 2026 18:44:10 +0100
Subject: [PATCH 5/5] fix(pdf): import PDFOptions as named type from puppeteer
v24
---
src/services/pdf-generation.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/services/pdf-generation.ts b/src/services/pdf-generation.ts
index ba603db5..d8708399 100644
--- a/src/services/pdf-generation.ts
+++ b/src/services/pdf-generation.ts
@@ -1,4 +1,4 @@
-import puppeteer from 'puppeteer';
+import puppeteer, { type PDFOptions } from 'puppeteer';
/**
* Generate a PDF from the provided HTML string using Puppeteer.
@@ -13,7 +13,7 @@ import puppeteer from 'puppeteer';
*/
export async function generatePDF(
html: string,
- options?: puppeteer.PDFOptions
+ options?: PDFOptions
): Promise {
// Launch a headless browser. The flags ensure compatibility in most CI
// and server environments without a sandbox.