Remove callApi, refactor API integrations, and adjust error handling

- Delete unused `callApi` utility and related imports across components.
- Replace `callApi` with direct `fetch` usage in `validateCustomer` and `addCustomer`.
- Update `customerRoutes` to include `/api` prefix for consistency.
- Refactor `useErrorHandler` to ensure comprehensive state management during errors.
- Improve `ErrorBoundary` component text for better clarity in fallback UI.
- Align `CustomersPage` logic with `useCallback` for optimized dependency management.
This commit is contained in:
2025-07-11 20:21:45 +02:00
parent 86be1e8920
commit 0724f3b1e7
9 changed files with 84 additions and 54 deletions

View File

@@ -1,5 +1,5 @@
export const customerRoutes = { export const customerRoutes = {
create: "/customers", create: "/api/customers",
validate: "/customers/validate", validate: "/api/customers/validate",
getById: (id: string) => `/customers/${id}`, getById: (id: string) => `/api/customers/${id}`,
}; };

View File

@@ -0,0 +1,10 @@
import {NextRequest, NextResponse} from "next/server";
import {serverCall} from "@/lib/api/serverCall";
import {customerRoutes} from "@/app/api/customers/customerRoutes";
export async function POST(req: NextRequest) {
const body = await req.json();
const result = await serverCall(customerRoutes.validate, "POST", body);
const data = await result.json();
return NextResponse.json(data);
}

View File

@@ -26,6 +26,7 @@ import {Customer} from "@/services/customers/entities/customer";
import Link from "next/link"; import Link from "next/link";
import {useErrorHandler} from "@/components/error-boundary"; import {useErrorHandler} from "@/components/error-boundary";
import {showError, showInfoToast} from "@/lib/ui/showToast"; import {showError, showInfoToast} from "@/lib/ui/showToast";
import {useCallback} from "react";
export default function CustomersPage() { export default function CustomersPage() {
const [customers, setCustomers] = useState<Customer[]>([]); const [customers, setCustomers] = useState<Customer[]>([]);
@@ -35,7 +36,8 @@ export default function CustomersPage() {
const pageSize = 15; const pageSize = 15;
const handleError = useErrorHandler(); const handleError = useErrorHandler();
const loadCustomers = async () => { // Wrap the loadCustomers function with useCallback
const loadCustomers = useCallback(async () => {
setLoading(true); setLoading(true);
try { try {
const response = await fetch('/api/customers'); const response = await fetch('/api/customers');
@@ -53,11 +55,11 @@ export default function CustomersPage() {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; }, [handleError]); // Include handleError as a dependency
useEffect(() => { useEffect(() => {
loadCustomers(); loadCustomers();
}, [handleError]); }, [loadCustomers]); // Add loadCustomers to the dependency array
const filtered = useMemo(() => { const filtered = useMemo(() => {
if (customers.length === 0) return []; if (customers.length === 0) return [];

View File

@@ -46,9 +46,9 @@ export function NewCustomerModal({ onCustomerCreated }: NewCustomerModalProps) {
city: string; city: string;
}; };
const emailExists = matches.some(m => m.email.toLowerCase() === email.toLowerCase()); const emailExists = Array.isArray(matches) && matches.some(m => m.email.toLowerCase() === email.toLowerCase());
const companyExists = matches.some(m => m.companyName.toLowerCase() === companyName.toLowerCase()); const companyExists = Array.isArray(matches) && matches.some(m => m.companyName.toLowerCase() === companyName.toLowerCase());
const addressExists = matches.some(m => const addressExists = Array.isArray(matches) && matches.some(m =>
m.street.toLowerCase() === street.toLowerCase() && m.street.toLowerCase() === street.toLowerCase() &&
m.zip.toLowerCase() === zip.toLowerCase() && m.zip.toLowerCase() === zip.toLowerCase() &&
m.city.toLowerCase() === city.toLowerCase() m.city.toLowerCase() === city.toLowerCase()
@@ -57,9 +57,10 @@ export function NewCustomerModal({ onCustomerCreated }: NewCustomerModalProps) {
const validateField = async () => { const validateField = async () => {
try { try {
const result = await validateCustomer({email, companyName, street, zip, city}); const result = await validateCustomer({email, companyName, street, zip, city});
setMatches(result); setMatches(result || []);
showInfoToast("Datenvalidierung abgeschlossen"); showInfoToast("Datenvalidierung abgeschlossen");
} catch (err) { } catch (err) {
setMatches([]); // Ensure matches is always an array
handleError(err); handleError(err);
} }
}; };
@@ -256,7 +257,7 @@ export function NewCustomerModal({ onCustomerCreated }: NewCustomerModalProps) {
{step === 1 ? ( {step === 1 ? (
<Button <Button
onClick={() => setStep(2)} onClick={() => setStep(2)}
disabled={!email || !name || !companyName || phoneNumbers.length === 0 || !phoneNumbers.some(p => p.number.trim()) || emailExists} // disabled={!email || !name || !companyName || phoneNumbers.length === 0 || !phoneNumbers.some(p => p.number.trim()) || emailExists}
> >
Weiter Weiter
</Button> </Button>

View File

@@ -99,9 +99,9 @@ function ErrorDialog({error, onReset, open}: ErrorDialogProps) {
}}> }}>
<DialogContent className="sm:max-w-md"> <DialogContent className="sm:max-w-md">
<DialogHeader> <DialogHeader>
<DialogTitle>Ein Fehler ist aufgetreten</DialogTitle> <DialogTitle>Uncaught Error!</DialogTitle>
<DialogDescription> <DialogDescription>
Es ist ein unerwarteter Fehler aufgetreten. Bitte versuchen Sie es erneut. Es ist ein unerwarteter Fehler aufgetreten.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
@@ -128,7 +128,16 @@ function ErrorDialog({error, onReset, open}: ErrorDialogProps) {
// Hook for handling async errors // Hook for handling async errors
export function useErrorHandler() { export function useErrorHandler() {
const [errorState, setErrorState] = React.useState();
console.log('Current state:', errorState);
return React.useCallback((error: unknown) => { return React.useCallback((error: unknown) => {
console.error("Async error caught:", error); console.error("Async error caught:", error);
// Trigger a re-render that will throw the error and be caught by the error boundary
setErrorState(() => {
throw error instanceof Error ? error : new Error(String(error));
});
}, []); }, []);
} }

View File

@@ -1,28 +0,0 @@
// lib/api/callApi.ts
import {serverCall} from "@/lib/api/serverCall";
export async function callApi<TResponse, TRequest = unknown>(
path: string,
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
body?: TRequest
): Promise<TResponse> {
const res = await serverCall(path, method, body);
const contentType = res.headers.get("content-type") ?? "";
const isJson = contentType.includes("application/json");
const rawBody = isJson ? await res.json() : await res.text();
console.log(`[api ${path}] Response:`, res.status, rawBody);
if (!res.ok) {
const errorMessage = isJson
? (rawBody?.message ?? rawBody?.errors?.join(", ")) ?? "Unbekannter Fehler"
: String(rawBody);
console.error(`[api ${path}] Error:`, errorMessage);
throw new Error(errorMessage);
}
return rawBody as TResponse;
}

View File

@@ -7,7 +7,7 @@ export async function serverCall(
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
body?: unknown body?: unknown
): Promise<Response> { ): Promise<Response> {
const url = `${process.env.INTERNAL_BACKEND_URL ?? "http://localhost:8080"}/api${path}`; const url = `${process.env.INTERNAL_BACKEND_URL ?? "http://localhost:8080"}${path}`;
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
const headers: Record<string, string> = { const headers: Record<string, string> = {

View File

@@ -1,8 +1,4 @@
"use server";
import {CreateCustomerDto} from "@/services/customers/dtos/createCustomer.dto"; import {CreateCustomerDto} from "@/services/customers/dtos/createCustomer.dto";
import {customerRoutes} from "@/app/api/customers/customerRoutes";
import {callApi} from "@/lib/api/callApi";
import {UUID} from "node:crypto"; import {UUID} from "node:crypto";
export async function addCustomer(params: CreateCustomerDto): Promise<void> { export async function addCustomer(params: CreateCustomerDto): Promise<void> {
@@ -19,6 +15,28 @@ export async function addCustomer(params: CreateCustomerDto): Promise<void> {
notes: notes.map(({text}) => ({text})), notes: notes.map(({text}) => ({text})),
}; };
const response = await callApi<UUID>(customerRoutes.create, "POST", payload); const res = await fetch('/api/customers', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
const contentType = res.headers.get("content-type") ?? "";
const isJson = contentType.includes("application/json");
const rawBody = isJson ? await res.json() : await res.text();
console.log(`[api /api/customers] Response:`, res.status, rawBody);
if (!res.ok) {
const errorMessage = isJson
? (rawBody?.message ?? rawBody?.errors?.join(", ")) ?? "Unbekannter Fehler"
: String(rawBody);
throw new Error(errorMessage);
}
const response = rawBody as UUID;
console.log(response); console.log(response);
} }

View File

@@ -1,7 +1,3 @@
"use server";
import {callApi} from "@/lib/api/callApi";
import {customerRoutes} from "@/app/api/customers/customerRoutes";
import {Customer} from "@/services/customers/entities/customer"; import {Customer} from "@/services/customers/entities/customer";
export async function validateCustomer(input: { export async function validateCustomer(input: {
@@ -15,5 +11,27 @@ export async function validateCustomer(input: {
return []; return [];
} }
return await callApi<Customer[]>(customerRoutes.validate, "POST", input); const res = await fetch('/api/customers/validate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(input),
});
const contentType = res.headers.get("content-type") ?? "";
const isJson = contentType.includes("application/json");
const rawBody = isJson ? await res.json() : await res.text();
console.log(`[api /api/customers/validate] Response:`, res.status, rawBody);
if (!res.ok) {
const errorMessage = isJson
? (rawBody?.message ?? rawBody?.errors?.join(", ")) ?? "Unbekannter Fehler"
: String(rawBody);
throw new Error(errorMessage);
}
return rawBody as Customer[];
} }