From 7825245901f3975b0dcd6ea31f7fd5b8e71fa316 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 14 Sep 2026 17:27:46 +0200 Subject: [PATCH] =?UTF-8?q?Fix:=20/dashboard=20500=20=E2=80=93=20buttonCls?= =?UTF-8?q?=20aus=20Client-Modul=20herausgel=C3=B6st?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Server-Komponenten (Dashboard, Aufträge, Suche, Checklisten, Auftragsdetail) riefen buttonCls() aus einem "use client"-Modul auf; das bricht zur Laufzeit (gemeldet von L8). Neu: src/components/work-orders/button-cls.ts (server-sicher), action-form re-exportiert. Nachweis: authentifizierter HTTP-Smoke (Backoffice 13 Seiten, Monteur 6 Seiten) alle 200, /dashboard vorher 500. tsc/lint grün. Co-Authored-By: Claude Opus 5 --- src/app/(app)/dashboard/page.tsx | 2 +- src/app/(app)/search/page.tsx | 2 +- src/app/(app)/settings/checklists/page.tsx | 3 ++- src/app/(app)/work-orders/[id]/page.tsx | 3 ++- src/app/(app)/work-orders/page.tsx | 3 ++- src/components/work-orders/action-form.tsx | 18 ++++-------------- src/components/work-orders/button-cls.ts | 21 +++++++++++++++++++++ src/components/work-orders/detail-tabs.tsx | 3 ++- 8 files changed, 35 insertions(+), 20 deletions(-) create mode 100644 src/components/work-orders/button-cls.ts diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx index 34ac022..0261dee 100644 --- a/src/app/(app)/dashboard/page.tsx +++ b/src/app/(app)/dashboard/page.tsx @@ -16,7 +16,7 @@ import { type LucideIcon, } from "lucide-react"; import { PageHead } from "@/components/mockup-ui"; -import { buttonCls } from "@/components/work-orders/action-form"; +import { buttonCls } from "@/components/work-orders/button-cls"; import { pageContext } from "@/components/work-orders/page-context"; import { Field, inputCls } from "@/components/work-orders/ui"; import { isoDay, parseListParams, toQuery, type Preset } from "@/lib/work-orders/filters"; diff --git a/src/app/(app)/search/page.tsx b/src/app/(app)/search/page.tsx index 46cc945..5f6742a 100644 --- a/src/app/(app)/search/page.tsx +++ b/src/app/(app)/search/page.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { getTranslations } from "next-intl/server"; import { Search } from "lucide-react"; import { PageHead } from "@/components/mockup-ui"; -import { buttonCls } from "@/components/work-orders/action-form"; +import { buttonCls } from "@/components/work-orders/button-cls"; import { pageContext } from "@/components/work-orders/page-context"; import { Empty, Field, inputCls, Section, StatusBadge } from "@/components/work-orders/ui"; import { isoDay, parseListParams } from "@/lib/work-orders/filters"; diff --git a/src/app/(app)/settings/checklists/page.tsx b/src/app/(app)/settings/checklists/page.tsx index 9c50ca3..090367f 100644 --- a/src/app/(app)/settings/checklists/page.tsx +++ b/src/app/(app)/settings/checklists/page.tsx @@ -4,7 +4,8 @@ import { z } from "zod"; import { Plus } from "lucide-react"; import { PageHead, Pill } from "@/components/mockup-ui"; import { Modal } from "@/components/modal"; -import { ActionForm, buttonCls } from "@/components/work-orders/action-form"; +import { ActionForm } from "@/components/work-orders/action-form"; +import { buttonCls } from "@/components/work-orders/button-cls"; import { SettingsTemplatesNav, templatesPageContext } from "@/components/work-orders/settings-nav"; import { Check, Empty, Field, inputCls, Section } from "@/components/work-orders/ui"; import { DEFAULT_CHECKLIST_ITEMS, DEFAULT_REQUIRED_PHOTOS } from "@/lib/work-orders/defaults"; diff --git a/src/app/(app)/work-orders/[id]/page.tsx b/src/app/(app)/work-orders/[id]/page.tsx index e840afa..9e3d7b0 100644 --- a/src/app/(app)/work-orders/[id]/page.tsx +++ b/src/app/(app)/work-orders/[id]/page.tsx @@ -3,7 +3,8 @@ import { notFound } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { ArrowLeft, Pencil, Siren, UsersRound } from "lucide-react"; import { Modal } from "@/components/modal"; -import { ActionForm, buttonCls } from "@/components/work-orders/action-form"; +import { ActionForm } from "@/components/work-orders/action-form"; +import { buttonCls } from "@/components/work-orders/button-cls"; import { ChecklistTab, DocumentsTab, diff --git a/src/app/(app)/work-orders/page.tsx b/src/app/(app)/work-orders/page.tsx index d5611f1..a6e7b21 100644 --- a/src/app/(app)/work-orders/page.tsx +++ b/src/app/(app)/work-orders/page.tsx @@ -3,7 +3,8 @@ import { getTranslations } from "next-intl/server"; import { LayoutGrid, Plus, RefreshCw, Rows3, X } from "lucide-react"; import { PageHead } from "@/components/mockup-ui"; import { Modal } from "@/components/modal"; -import { ActionForm, buttonCls } from "@/components/work-orders/action-form"; +import { ActionForm } from "@/components/work-orders/action-form"; +import { buttonCls } from "@/components/work-orders/button-cls"; import { pageContext } from "@/components/work-orders/page-context"; import { Empty, Field, GroupIcon, inputCls, LinkTabs } from "@/components/work-orders/ui"; import { WorkOrderFields } from "@/components/work-orders/work-order-fields"; diff --git a/src/components/work-orders/action-form.tsx b/src/components/work-orders/action-form.tsx index 4606956..ef1e63c 100644 --- a/src/components/work-orders/action-form.tsx +++ b/src/components/work-orders/action-form.tsx @@ -6,22 +6,12 @@ import { AlertCircle, CheckCircle2 } from "lucide-react"; import { IDLE_STATE, type ActionState } from "@/lib/work-orders/action-state"; import type { CompletionBlocker } from "@/lib/work-orders/status"; import { cn } from "@/lib/utils"; +import { buttonCls, type ButtonVariant } from "@/components/work-orders/button-cls"; + +export { buttonCls }; type Action = (prev: ActionState, fd: FormData) => Promise; -const VARIANTS = { - primary: "bg-cta text-cta-foreground hover:opacity-90", - default: "bg-primary text-primary-foreground hover:opacity-90", - outline: "border border-border bg-background hover:bg-muted", - danger: "border border-[var(--risk)] bg-background text-[var(--risk)] hover:bg-muted", - ghost: "hover:bg-muted text-muted-foreground", -} as const; - -export const buttonCls = (variant: keyof typeof VARIANTS = "default") => - cn( - "inline-flex min-h-11 items-center justify-center gap-2 rounded-lg px-4 font-heading text-sm font-semibold transition-colors focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none disabled:opacity-50", - VARIANTS[variant], - ); /** * Form bound to a work order server action. Shows translated errors (incl. structured @@ -44,7 +34,7 @@ export function ActionForm({ submitLabel: string; pendingLabel?: string; successText?: string; - variant?: keyof typeof VARIANTS; + variant?: ButtonVariant; namespace?: "workOrders" | "settingsTemplates"; className?: string; footerClassName?: string; diff --git a/src/components/work-orders/button-cls.ts b/src/components/work-orders/button-cls.ts new file mode 100644 index 0000000..c668591 --- /dev/null +++ b/src/components/work-orders/button-cls.ts @@ -0,0 +1,21 @@ +import { cn } from "@/lib/utils"; + +/** + * Button class names shared by server and client components. Kept out of "use client" modules: + * calling a function exported from a client module inside a server component fails at runtime. + */ +const VARIANTS = { + primary: "bg-cta text-cta-foreground hover:opacity-90", + default: "bg-primary text-primary-foreground hover:opacity-90", + outline: "border border-border bg-background hover:bg-muted", + danger: "border border-[var(--risk)] bg-background text-[var(--risk)] hover:bg-muted", + ghost: "hover:bg-muted text-muted-foreground", +} as const; + +export type ButtonVariant = keyof typeof VARIANTS; + +export const buttonCls = (variant: ButtonVariant = "default") => + cn( + "inline-flex min-h-11 items-center justify-center gap-2 rounded-lg px-4 font-heading text-sm font-semibold transition-colors focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none disabled:opacity-50", + VARIANTS[variant], + ); diff --git a/src/components/work-orders/detail-tabs.tsx b/src/components/work-orders/detail-tabs.tsx index 6700197..ac9dbdf 100644 --- a/src/components/work-orders/detail-tabs.tsx +++ b/src/components/work-orders/detail-tabs.tsx @@ -27,7 +27,8 @@ import { removePhotoRequirementAction, } from "@/server/actions/work_orders/work-orders"; import { formatDate, formatDateTime } from "@/lib/work-orders/time"; -import { ActionForm, buttonCls } from "@/components/work-orders/action-form"; +import { ActionForm } from "@/components/work-orders/action-form"; +import { buttonCls } from "@/components/work-orders/button-cls"; import { Check, Dl, Empty, Field, inputCls, Section } from "@/components/work-orders/ui"; import type { CompletionBlocker } from "@/lib/work-orders/status";