"use client"; import { useRef, useTransition } from "react"; import { Send, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/src/components/ui/button"; import { Input } from "@/src/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/src/components/ui/select"; import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from "@/src/components/ui/sheet"; import { Switch } from "@/src/components/ui/switch"; import { cn } from "@/src/lib/utils"; import { sendMarketingTestEmail } from "../../actions/editor-actions"; import { EMAIL_TEMPLATES, MILESTONE_LIMITS, PLACEHOLDERS, REWARD_APPLIES_TO_LABELS, TRIGGER_DESCRIPTIONS, TRIGGER_KINDS, TRIGGER_LABELS, WINBACK_LIMITS, emailConfigSchema, type Reward, } from "../../lib/node-config"; import { isTriggerNode, type EmailFlowNode, type FlowNode, type TriggerFlowNode, } from "./flow/flow-model"; interface NodeSettingsSheetProps { node: FlowNode | null; businessId: string; emailOptions: { id: string; name: string }[]; /** Nur Trigger: die verbundene E-Mail */ connectedEmailId: string | null; onClose: () => void; onConfigChange: (nodeId: string, config: Record) => void; onActiveChange: (nodeId: string, isActive: boolean) => void; onConnect: (triggerId: string, emailId: string | null) => void; onDelete: (nodeId: string) => void; } const fieldClass = "bg-white md:text-sm"; /** Einstellungen des angeklickten Bausteins, rechts als Panel. */ const NodeSettingsSheet = (props: NodeSettingsSheetProps) => { const { node, onClose } = props; return ( !open && onClose()}> {node && isTriggerNode(node) && } {node && !isTriggerNode(node) && } ); }; // ─── Trigger ───────────────────────────────────────────────────────────────── const NONE = "__none__"; const TriggerSettings = ({ node, emailOptions, connectedEmailId, onConfigChange, onActiveChange, onConnect, onDelete, }: NodeSettingsSheetProps & { node: TriggerFlowNode }) => { const { kind, config, isActive, sent, activatedAt } = node.data; const numberField = (key: "bookings" | "days", limits: { min: number; max: number }) => { const value = typeof config[key] === "number" ? (config[key] as number) : ""; const invalid = typeof value !== "number" || value < limits.min || value > limits.max; return ( onConfigChange(node.id, { ...config, [key]: e.target.value === "" ? "" : Math.round(Number(e.target.value)), }) } className={cn(fieldClass, "w-24")} /> ); }; return ( <> {TRIGGER_LABELS[kind]} {TRIGGER_DESCRIPTIONS[kind]}
{kind === "milestone" && ( )} {kind === "winback" && ( )} {kind === "birthday" && (

Wer am 29. Februar Geburtstag hat, bekommt die Mail in anderen Jahren am 28. Februar.

)}
Führt zu

Eingeschaltet

{activatedAt ? `Läuft seit ${new Date(activatedAt).toLocaleDateString("de-DE")}` : "Wirkt nach dem Speichern"}

onActiveChange(node.id, checked)} />

Gezählt wird erst ab dem Einschalten. Wer vorher schon so weit war, bekommt keine Mail - so werden beim Einschalten nicht alle Bestandskunden auf einmal angeschrieben. Mails gehen nur an Kunden, die zugestimmt haben, und höchstens eine pro Tag.

{sent} {sent === 1 ? "Mail" : "Mails"} über diesen Trigger verschickt

); }; // ─── E-Mail ────────────────────────────────────────────────────────────────── const DEFAULT_REWARD: Reward = { type: "percent", value: 10, validDays: 30, appliesTo: "all" }; const EmailSettings = ({ node, businessId, onConfigChange, onDelete, }: NodeSettingsSheetProps & { node: EmailFlowNode }) => { const config = node.data.config; const reward = (config.reward ?? null) as Reward | null; const bodyRef = useRef(null); const [isSending, startSending] = useTransition(); const parsed = emailConfigSchema.safeParse(config); const errors = new Map(); if (!parsed.success) { for (const issue of parsed.error.issues) { const key = issue.path.join("."); if (!errors.has(key)) errors.set(key, issue.message); } } const set = (patch: Record) => onConfigChange(node.id, { ...config, ...patch }); const setReward = (patch: Partial) => set({ reward: { ...(reward ?? DEFAULT_REWARD), ...patch } }); const insertPlaceholder = (key: string) => { const body = typeof config.body === "string" ? config.body : ""; const textarea = bodyRef.current; const start = textarea?.selectionStart ?? body.length; const end = textarea?.selectionEnd ?? body.length; set({ body: body.slice(0, start) + key + body.slice(end) }); requestAnimationFrame(() => { textarea?.focus(); textarea?.setSelectionRange(start + key.length, start + key.length); }); }; const sendTest = () => { startSending(async () => { const result = await sendMarketingTestEmail(businessId, config); if (result.success) toast.success(`Testmail an ${result.to} verschickt.`); else toast.error(result.error); }); }; const error = (key: string) => errors.get(key) ?

{errors.get(key)}

: null; return ( <> E-Mail Absender ist dein Studio, Antworten landen bei deiner Studio-E-Mail.
Vorlage einsetzen