From 44fb8837476c58ff052a40ee337c36e73a798ed5 Mon Sep 17 00:00:00 2001 From: wm Date: Tue, 17 Mar 2026 12:23:47 +0100 Subject: [PATCH] feat: statisches Chat-Mockup durch echten Sales-Chat ersetzt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HeroSalesChat.tsx: funktionierender Chat mit Webhook-Anbindung, Typing-Indikator, Fehlerbehandlung und Enter-to-Send - useChatSession.ts: Session-ID via crypto.randomUUID() + localStorage - Hero.tsx: statischen Block durch ersetzt, linke Seite vollstΓ€ndig erhalten Co-Authored-By: Claude Sonnet 4.6 --- landing-react/src/components/Hero.tsx | 63 +------ .../src/components/HeroSalesChat.tsx | 167 ++++++++++++++++++ landing-react/src/hooks/useChatSession.ts | 19 ++ 3 files changed, 190 insertions(+), 59 deletions(-) create mode 100644 landing-react/src/components/HeroSalesChat.tsx create mode 100644 landing-react/src/hooks/useChatSession.ts diff --git a/landing-react/src/components/Hero.tsx b/landing-react/src/components/Hero.tsx index 9500f46..b35e661 100644 --- a/landing-react/src/components/Hero.tsx +++ b/landing-react/src/components/Hero.tsx @@ -1,4 +1,5 @@ -import { Rocket, Play, Info, Bot, Circle, Send } from 'lucide-react' +import { Rocket, Play, Info } from 'lucide-react' +import HeroSalesChat from './HeroSalesChat' export default function Hero() { const scrollTo = (id: string) => { @@ -92,65 +93,9 @@ export default function Hero() { - {/* Chat Preview Widget */} + {/* Live Sales Chat */}
-
- {/* Chat Header */} -
-
- -
-
- BotKonzept Assistent - - - Online - -
-
- - {/* Messages */} -
-
- Hallo! πŸ‘‹ Wie kann ich Ihnen heute helfen? -
-
- Was sind Ihre Γ–ffnungszeiten? -
-
- Unsere Γ–ffnungszeiten sind Montag bis Freitag von 9:00 bis 18:00 Uhr. Am - Wochenende sind wir geschlossen. -
-
- Wie kann ich eine Bestellung aufgeben? -
- {/* Typing Indicator */} -
- - - -
-
- - {/* Chat Input */} -
- - -
-
+
diff --git a/landing-react/src/components/HeroSalesChat.tsx b/landing-react/src/components/HeroSalesChat.tsx new file mode 100644 index 0000000..5717cb4 --- /dev/null +++ b/landing-react/src/components/HeroSalesChat.tsx @@ -0,0 +1,167 @@ +import { useState, useRef, useEffect } from 'react' +import { Bot, Circle, Send, AlertCircle } from 'lucide-react' +import { useChatSession } from '../hooks/useChatSession' + +// ─── Webhook-Endpunkt ──────────────────────────────────────────────────────── +const WEBHOOK_URL = 'HIER_N8N_CHAT_WEBHOOK_EINTRAGEN' +// ──────────────────────────────────────────────────────────────────────────── + +interface Message { + id: string + role: 'bot' | 'user' + text: string +} + +const GREETING: Message = { + id: 'greeting', + role: 'bot', + text: 'Hallo πŸ‘‹ Wie kann ich Ihnen heute helfen?', +} + +export default function HeroSalesChat() { + const sessionId = useChatSession() + const [messages, setMessages] = useState([GREETING]) + const [input, setInput] = useState('') + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + const bottomRef = useRef(null) + + // Automatisch zum neuesten Eintrag scrollen + useEffect(() => { + bottomRef.current?.scrollIntoView({ behavior: 'smooth' }) + }, [messages, isLoading]) + + const sendMessage = async () => { + const text = input.trim() + if (!text || isLoading) return + + setInput('') + setError(null) + + const userMsg: Message = { id: crypto.randomUUID(), role: 'user', text } + setMessages((prev) => [...prev, userMsg]) + setIsLoading(true) + + try { + const res = await fetch(WEBHOOK_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message: text, sessionId }), + }) + + if (!res.ok) throw new Error(`HTTP ${res.status}`) + + const contentType = res.headers.get('content-type') ?? '' + let botText = '' + + if (contentType.includes('application/json')) { + const data = await res.json() + // Bekannte Antwortformate abfangen + botText = + data?.message ?? + data?.text ?? + data?.reply ?? + data?.output ?? + JSON.stringify(data) + } else { + botText = (await res.text()).trim() + } + + const botMsg: Message = { id: crypto.randomUUID(), role: 'bot', text: botText } + setMessages((prev) => [...prev, botMsg]) + } catch (err) { + console.error('[Chat-Fehler]', err) + setError('Verbindungsfehler – bitte versuchen Sie es erneut.') + } finally { + setIsLoading(false) + } + } + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') sendMessage() + } + + return ( +
+ {/* ── Header ── */} +
+
+ +
+
+ BotKonzept Assistent + + + Online + +
+
+ + {/* ── Nachrichtenbereich ── */} +
+ {messages.map((msg) => + msg.role === 'bot' ? ( +
+ {msg.text} +
+ ) : ( +
+ {msg.text} +
+ ), + )} + + {/* Typing-Indikator */} + {isLoading && ( +
+ + + +
+ )} + + {/* Fehlermeldung */} + {error && ( +
+ + {error} +
+ )} + + {/* Scroll-Anker */} +
+
+ + {/* ── Eingabe ── */} +
+ setInput(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Nachricht eingeben..." + disabled={isLoading} + className="flex-1 px-4 py-3 border border-gray-300 rounded-full text-[0.9375rem] outline-none focus:border-primary transition-colors disabled:opacity-50" + /> + +
+
+ ) +} diff --git a/landing-react/src/hooks/useChatSession.ts b/landing-react/src/hooks/useChatSession.ts new file mode 100644 index 0000000..2ceeaae --- /dev/null +++ b/landing-react/src/hooks/useChatSession.ts @@ -0,0 +1,19 @@ +import { useState } from 'react' + +const STORAGE_KEY = 'bk_chat_session_id' + +/** + * Gibt eine stabile Session-ID zurΓΌck. + * Beim ersten Aufruf wird eine neue UUID erzeugt und in localStorage gespeichert. + * Bei jedem weiteren Seitenaufruf wird dieselbe ID wiederverwendet. + */ +export function useChatSession(): string { + const [sessionId] = useState(() => { + const stored = localStorage.getItem(STORAGE_KEY) + if (stored) return stored + const newId = crypto.randomUUID() + localStorage.setItem(STORAGE_KEY, newId) + return newId + }) + return sessionId +}