diff --git a/.gitignore b/.gitignore index 854ac21..dd69fbd 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,6 @@ next-env.d.ts # Uploaded Belege (persistent volume in production) data/documents/ + +.vscode/ +.react-router diff --git a/.playwright-mcp/console-2026-05-02T20-43-04-874Z.log b/.playwright-mcp/console-2026-05-02T20-43-04-874Z.log new file mode 100644 index 0000000..eb0e894 --- /dev/null +++ b/.playwright-mcp/console-2026-05-02T20-43-04-874Z.log @@ -0,0 +1 @@ +[ 421ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:3000/favicon.ico:0 diff --git a/.playwright-mcp/page-2026-05-02T20-43-05-337Z.yml b/.playwright-mcp/page-2026-05-02T20-43-05-337Z.yml new file mode 100644 index 0000000..19949f3 --- /dev/null +++ b/.playwright-mcp/page-2026-05-02T20-43-05-337Z.yml @@ -0,0 +1,19 @@ +- generic [ref=e4]: + - generic [ref=e5]: + - img [ref=e7] + - generic [ref=e9]: Rechnungsmanager + - generic [ref=e10]: + - heading "Willkommen zurück" [level=1] [ref=e11] + - paragraph [ref=e12]: Benutzername oder E-Mail und Passwort eingeben + - generic [ref=e14]: + - generic [ref=e15]: + - text: Benutzername oder E-Mail + - textbox "Benutzername oder E-Mail" [ref=e16]: + - /placeholder: anna oder anna@example.de + - generic [ref=e17]: + - text: Passwort + - generic [ref=e18]: + - textbox "Passwort" [ref=e19] + - button "Passwort anzeigen" [ref=e20]: + - img [ref=e21] + - button "Anmelden" [ref=e24] \ No newline at end of file diff --git a/INTEGRATION_EXAMPLE.md b/INTEGRATION_EXAMPLE.md new file mode 100644 index 0000000..8157a7b --- /dev/null +++ b/INTEGRATION_EXAMPLE.md @@ -0,0 +1,95 @@ +/** + * INTEGRATION EXAMPLE: How to use error logging in your existing routes + * This shows the new error-logger.server in real-world usage + */ + +// Before (old code - minimal logging): +/* +export async function action({ request }: { request: Request }) { + const user = await getApiUser(request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + + const body = await request.json(); + const parsed = customerSchema.safeParse(body); + if (!parsed.success) return Response.json({ error: parsed.error.issues }, { status: 400 }); + + try { + const customer = await prisma.customer.create({ data: parsed.data }); + await log({ userId: user.id, action: "CREATE_CUSTOMER", entity: "Customer", entityId: customer.id, request }); + return Response.json(customer, { status: 201 }); + } catch (error) { + console.error(error); // ❌ Not helpful for debugging + return Response.json({ error: "Internal server error" }, { status: 500 }); + } +} +*/ + +// After (with comprehensive error logging): +import { getApiUser } from "@/session.server"; +import prisma from "@/lib/prisma.server"; +import { log } from "@/lib/logger.server"; +import { logApiError } from "@/lib/error-logger.server"; +import { customerSchema } from "@/lib/schemas"; + +export async function action({ request }: { request: Request }) { + const user = await getApiUser(request); + if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 }); + + try { + const body = await request.json(); + const parsed = customerSchema.safeParse(body); + if (!parsed.success) { + return Response.json({ error: parsed.error.issues }, { status: 400 }); + } + + const customer = await prisma.customer.create({ data: parsed.data }); + await log({ + userId: user.id, + action: "CREATE_CUSTOMER", + entity: "Customer", + entityId: customer.id, + request, + }); + return Response.json(customer, { status: 201 }); + } catch (error) { + // ✅ Now with full context: stack trace, request details, metadata + logApiError(error, { + request, + endpoint: "/api/customers", + userId: user.id, + statusCode: 500, + metadata: { + method: request.method, + }, + }); + return Response.json({ error: "Internal server error" }, { status: 500 }); + } +} + +// ============================================================================ +// WHAT YOU'LL SEE IN THE SERVER LOGS NOW: +// ============================================================================ + +/* +================================================================================ +[API_ERROR] | 2026-05-02T22:15:45.789Z | duplicate entry for unique field 'email' +POST /api/customers | user: user-abc123 | ip: 192.168.1.100 | endpoint: /api/customers | statusCode: 500 +Error: Customer with email 'john@example.com' already exists + at Object.create (file:///app/lib/customer.server.ts:25:13) + at action (file:///app/routes/api.customers.ts:30:7) + at processRequest (file:///app/routes/_middleware.ts:12:3) + +Metadata: { + "method": "POST", + "endpoint": "/api/customers" +} +================================================================================ + +✓ You can now see: + - Exact error message with context + - HTTP method & endpoint + - User ID & IP address + - Stack trace for debugging + - Custom metadata + - Timestamp +*/ diff --git a/app/components/company/company-form.tsx b/app/components/company/company-form.tsx index 1080593..9761614 100644 --- a/app/components/company/company-form.tsx +++ b/app/components/company/company-form.tsx @@ -4,6 +4,8 @@ import { z } from "zod"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { debugLog, handleApiError } from "@/lib/client-validation"; +import { useState, useEffect } from "react"; const schema = z.object({ name: z.string().min(1, "Name ist erforderlich"), @@ -32,118 +34,317 @@ interface CompanyFormProps { submitLabel?: string; } -function Field({ label, error, children }: { label: string; error?: string; children: React.ReactNode }) { +function Field({ + label, + error, + tooltip, + required = false, + children +}: { + label: string; + error?: string; + tooltip?: string; + required?: boolean; + children: React.ReactNode +}) { + const [showRequiredTooltip, setShowRequiredTooltip] = useState(false); + + // Debug: log errors to console + if (error) { + console.log(`[Field Error] ${label}: ${error}`); + } + return (
{error}
} + +{error}
+ {tooltip &&{tooltip}
} +