fix: surface specific business-rule error messages to beekeepers

Backend business-rule guards (inactive apiary blocking colony
create/move, active colonies blocking apiary deactivation, negative
quantity on IN/OUT stock movements, unavailable queen blocking colony
assignment, inactive colony blocking split/merge/move) threw English,
UUID-laden exception text that the frontend either discarded in favor
of a generic message or - in colony-actions.tsx's move/split/merge/
assign-queen dialogs - didn't handle at all, so the guard fired with
zero visible feedback. Translated the messages to plain German and
wired the affected mutations to display them, so a beekeeper sees the
actual reason a save was rejected instead of silence or "failed".

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Sonnet 5 committed 2026-09-09 11:38:35 +02:00
1 parent de5df72491
commit 997dadfaaa
8 files changed
+37 -11

No files matched your search

@@ -5,6 +5,7 @@ import { MapPin } from "lucide-react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { deactivateApiary, reactivateApiary, updateApiary } from "@/lib/apiaries/api";
import { ApiError } from "@/lib/auth/api-client";
import { Apiary } from "@/lib/apiaries/types";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
@@ -54,7 +55,10 @@ export function ApiaryEditCard({ apiary }: { apiary: Apiary }) {
description: "Er kann jederzeit über „Reaktivieren“ wiederhergestellt werden.",
});
},
onError: () => toast.error("Bienenstand konnte nicht deaktiviert werden"),
onError: (error) =>
toast.error(
error instanceof ApiError ? error.message : "Bienenstand konnte nicht deaktiviert werden",
),
});
const reactivateMutation = useMutation({
@@ -3,6 +3,7 @@
import { FormEvent, useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { ApiError } from "@/lib/auth/api-client";
import {
dissolveColony,
mergeColony,
@@ -35,6 +36,10 @@ import {
SelectValue,
} from "@/components/ui/select";
function errorMessage(error: unknown, fallback: string): string {
return error instanceof ApiError ? error.message : fallback;
}
function invalidateColony(
queryClient: ReturnType<typeof useQueryClient>,
colonyId: string,
@@ -59,6 +64,8 @@ function MoveDialog({ colony }: { colony: Colony }) {
invalidateColony(queryClient, colony.id);
setOpen(false);
},
onError: (error) =>
toast.error(errorMessage(error, "Volk konnte nicht umgesetzt werden")),
});
return (
@@ -122,6 +129,8 @@ function SplitDialog({ colony }: { colony: Colony }) {
void queryClient.invalidateQueries({ queryKey: ["colonies"] });
setOpen(false);
},
onError: (error) =>
toast.error(errorMessage(error, "Ableger konnte nicht angelegt werden")),
});
return (
@@ -196,6 +205,8 @@ function AssignQueenDialog({ colony }: { colony: Colony }) {
void queryClient.invalidateQueries({ queryKey: ["queens"] });
setOpen(false);
},
onError: (error) =>
toast.error(errorMessage(error, "Königin konnte nicht zugewiesen werden")),
});
return (
@@ -259,6 +270,8 @@ function MergeDialog({ colony }: { colony: Colony }) {
void queryClient.invalidateQueries({ queryKey: ["colonies"] });
setOpen(false);
},
onError: (error) =>
toast.error(errorMessage(error, "Völker konnten nicht vereinigt werden")),
});
return (
@@ -4,6 +4,7 @@ import { FormEvent, useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { assignQueenColony } from "@/lib/queens/api";
import { ApiError } from "@/lib/auth/api-client";
import { QUEEN_UNAVAILABLE_STATUSES } from "@/lib/queens/labels";
import { QueenStatus } from "@/lib/queens/types";
import { fetchColonies } from "@/lib/colonies/api";
@@ -45,7 +46,10 @@ export function AssignColonyDialog({
void queryClient.invalidateQueries({ queryKey: ["queens", queenId, "history"] });
setOpen(false);
},
onError: () => toast.error("Volk konnte nicht zugewiesen werden"),
onError: (error) =>
toast.error(
error instanceof ApiError ? error.message : "Volk konnte nicht zugewiesen werden",
),
});
return (
@@ -3,6 +3,7 @@
import { FormEvent, useState } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { addStockMovement } from "@/lib/storage/api";
import { ApiError } from "@/lib/auth/api-client";
import { StockMovementType } from "@/lib/storage/types";
import { STOCK_MOVEMENT_TYPES, STOCK_MOVEMENT_TYPE_LABELS } from "@/lib/storage/labels";
import { Button } from "@/components/ui/button";
@@ -40,8 +41,10 @@ export function RecordMovementDialog({ stockItemId }: { stockItemId: string }) {
setQuantity("");
setError(null);
},
onError: () => {
setError("Bewegung konnte nicht gespeichert werden.");
onError: (error) => {
setError(
error instanceof ApiError ? error.message : "Bewegung konnte nicht gespeichert werden.",
);
},
});