feat: BotKonzept Frontend erstellt
Neue Dateien: - index.html: Landing Page mit Registrierung - dashboard.html: Kunden-Dashboard - css/style.css: Haupt-Stylesheet (1500+ Zeilen) - css/dashboard.css: Dashboard-Styles (800+ Zeilen) - js/main.js: Landing Page JavaScript - js/dashboard.js: Dashboard JavaScript - logo/20250119_Logo_Botkozept.svg: Logo Features: - Modernes, responsives Design - Hero-Section mit Chat-Preview Animation - Feature-Übersicht und Pricing-Tabelle - Registrierungsformular mit Validierung - FAQ-Akkordeon - Dashboard mit PDF-Upload (Drag & Drop) - Chatbot-Test-Interface - Embed-Code Generator - Trial-Status und Upgrade-Banner - Mobile-optimiert
This commit is contained in:
744
js/dashboard.js
Normal file
744
js/dashboard.js
Normal file
@@ -0,0 +1,744 @@
|
||||
/**
|
||||
* BotKonzept Dashboard JavaScript
|
||||
* ================================
|
||||
* Handles all dashboard functionality
|
||||
*/
|
||||
|
||||
// Configuration
|
||||
const DASHBOARD_CONFIG = {
|
||||
// API Endpoints - Update for production
|
||||
API_BASE_URL: 'https://api.botkonzept.de',
|
||||
CHAT_WEBHOOK_URL: '', // Will be set from customer data
|
||||
UPLOAD_URL: '', // Will be set from customer data
|
||||
|
||||
// Customer data (loaded from localStorage or API)
|
||||
customerId: null,
|
||||
customerData: null,
|
||||
|
||||
// Trial settings
|
||||
trialDays: 7,
|
||||
discountDay3: 0.30, // 30% discount
|
||||
discountDay5: 0.15, // 15% discount
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// DOM Ready
|
||||
// ============================================
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initDashboard();
|
||||
initNavigation();
|
||||
initUserMenu();
|
||||
initFileUpload();
|
||||
initChatbot();
|
||||
initSectionNavigation();
|
||||
loadCustomerData();
|
||||
updateTrialStatus();
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Dashboard Initialization
|
||||
// ============================================
|
||||
function initDashboard() {
|
||||
// Check if user is logged in (for demo, we'll simulate this)
|
||||
const isLoggedIn = localStorage.getItem('botkonzept_session') || true; // Demo mode
|
||||
|
||||
if (!isLoggedIn) {
|
||||
window.location.href = 'index.html';
|
||||
return;
|
||||
}
|
||||
|
||||
// Load customer data from localStorage or use demo data
|
||||
const storedData = localStorage.getItem('botkonzept_customer');
|
||||
if (storedData) {
|
||||
DASHBOARD_CONFIG.customerData = JSON.parse(storedData);
|
||||
} else {
|
||||
// Demo data
|
||||
DASHBOARD_CONFIG.customerData = {
|
||||
id: 'demo-' + Date.now(),
|
||||
firstName: 'Max',
|
||||
lastName: 'Mustermann',
|
||||
email: 'max@beispiel.de',
|
||||
company: 'Muster GmbH',
|
||||
trialStartDate: new Date().toISOString(),
|
||||
instanceUrl: 'https://sb-demo.userman.de',
|
||||
chatWebhook: 'https://sb-demo.userman.de/webhook/rag-chat-webhook/chat',
|
||||
uploadUrl: 'https://sb-demo.userman.de/form/rag-upload-form',
|
||||
};
|
||||
}
|
||||
|
||||
// Update UI with customer data
|
||||
updateCustomerUI();
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Navigation
|
||||
// ============================================
|
||||
function initNavigation() {
|
||||
// Sidebar navigation
|
||||
const navItems = document.querySelectorAll('.sidebar-nav .nav-item');
|
||||
const sections = document.querySelectorAll('.dashboard-section');
|
||||
|
||||
navItems.forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const sectionId = item.getAttribute('data-section');
|
||||
|
||||
// Update active states
|
||||
navItems.forEach(nav => nav.classList.remove('active'));
|
||||
item.classList.add('active');
|
||||
|
||||
// Show corresponding section
|
||||
sections.forEach(section => {
|
||||
section.classList.remove('active');
|
||||
if (section.id === sectionId) {
|
||||
section.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Update URL hash
|
||||
window.location.hash = sectionId;
|
||||
});
|
||||
});
|
||||
|
||||
// Handle initial hash
|
||||
const hash = window.location.hash.slice(1);
|
||||
if (hash) {
|
||||
const targetNav = document.querySelector(`[data-section="${hash}"]`);
|
||||
if (targetNav) {
|
||||
targetNav.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initSectionNavigation() {
|
||||
// Quick action cards
|
||||
const actionCards = document.querySelectorAll('.action-card');
|
||||
actionCards.forEach(card => {
|
||||
card.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const sectionId = card.getAttribute('data-section');
|
||||
const targetNav = document.querySelector(`[data-section="${sectionId}"]`);
|
||||
if (targetNav) {
|
||||
targetNav.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// User Menu
|
||||
// ============================================
|
||||
function initUserMenu() {
|
||||
const userBtn = document.getElementById('userMenuBtn');
|
||||
const userDropdown = document.getElementById('userDropdown');
|
||||
|
||||
if (!userBtn || !userDropdown) return;
|
||||
|
||||
userBtn.addEventListener('click', () => {
|
||||
userDropdown.classList.toggle('active');
|
||||
});
|
||||
|
||||
// Close on outside click
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!userBtn.contains(e.target) && !userDropdown.contains(e.target)) {
|
||||
userDropdown.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Customer Data
|
||||
// ============================================
|
||||
function loadCustomerData() {
|
||||
const data = DASHBOARD_CONFIG.customerData;
|
||||
if (!data) return;
|
||||
|
||||
// Update customer ID in embed code
|
||||
const customerIdEl = document.getElementById('customerId');
|
||||
if (customerIdEl) {
|
||||
customerIdEl.textContent = data.id;
|
||||
}
|
||||
|
||||
// Update webhook URLs
|
||||
const chatWebhookEl = document.getElementById('chatWebhook');
|
||||
if (chatWebhookEl && data.chatWebhook) {
|
||||
chatWebhookEl.textContent = data.chatWebhook;
|
||||
}
|
||||
|
||||
const uploadUrlEl = document.getElementById('uploadUrl');
|
||||
if (uploadUrlEl && data.uploadUrl) {
|
||||
uploadUrlEl.textContent = data.uploadUrl;
|
||||
}
|
||||
|
||||
// Update user name
|
||||
const userNameEl = document.getElementById('userName');
|
||||
if (userNameEl) {
|
||||
userNameEl.textContent = `${data.firstName} ${data.lastName}`;
|
||||
}
|
||||
|
||||
// Update account email
|
||||
const accountEmailEl = document.getElementById('accountEmail');
|
||||
if (accountEmailEl) {
|
||||
accountEmailEl.value = data.email;
|
||||
}
|
||||
}
|
||||
|
||||
function updateCustomerUI() {
|
||||
loadCustomerData();
|
||||
|
||||
// Update stats (demo data)
|
||||
updateStats({
|
||||
documents: 0,
|
||||
messages: 0,
|
||||
visitors: 0,
|
||||
satisfaction: '-'
|
||||
});
|
||||
}
|
||||
|
||||
function updateStats(stats) {
|
||||
const docCountEl = document.getElementById('docCount');
|
||||
const messageCountEl = document.getElementById('messageCount');
|
||||
const visitorCountEl = document.getElementById('visitorCount');
|
||||
const satisfactionEl = document.getElementById('satisfactionRate');
|
||||
|
||||
if (docCountEl) docCountEl.textContent = stats.documents;
|
||||
if (messageCountEl) messageCountEl.textContent = stats.messages;
|
||||
if (visitorCountEl) visitorCountEl.textContent = stats.visitors;
|
||||
if (satisfactionEl) satisfactionEl.textContent = stats.satisfaction;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Trial Status
|
||||
// ============================================
|
||||
function updateTrialStatus() {
|
||||
const data = DASHBOARD_CONFIG.customerData;
|
||||
if (!data || !data.trialStartDate) return;
|
||||
|
||||
const trialStart = new Date(data.trialStartDate);
|
||||
const now = new Date();
|
||||
const daysPassed = Math.floor((now - trialStart) / (1000 * 60 * 60 * 24));
|
||||
const daysRemaining = Math.max(0, DASHBOARD_CONFIG.trialDays - daysPassed);
|
||||
|
||||
// Update trial badge
|
||||
const trialDaysEl = document.getElementById('trialDays');
|
||||
if (trialDaysEl) {
|
||||
trialDaysEl.textContent = daysRemaining;
|
||||
}
|
||||
|
||||
// Update trial end date
|
||||
const trialEndDateEl = document.getElementById('trialEndDate');
|
||||
if (trialEndDateEl) {
|
||||
const endDate = new Date(trialStart);
|
||||
endDate.setDate(endDate.getDate() + DASHBOARD_CONFIG.trialDays);
|
||||
trialEndDateEl.textContent = formatDate(endDate);
|
||||
}
|
||||
|
||||
// Show/hide discount banner based on days
|
||||
const trialBanner = document.getElementById('trialBanner');
|
||||
if (trialBanner) {
|
||||
if (daysPassed <= 3) {
|
||||
// Show 30% discount
|
||||
trialBanner.querySelector('h3').textContent = '🎉 Exklusives Angebot: 30% Frühbucher-Rabatt!';
|
||||
trialBanner.querySelector('.btn').textContent = 'Jetzt für €34,30/Monat upgraden';
|
||||
} else if (daysPassed <= 5) {
|
||||
// Show 15% discount
|
||||
trialBanner.querySelector('h3').textContent = '⏰ Nur noch 2 Tage: 15% Rabatt!';
|
||||
trialBanner.querySelector('.btn').textContent = 'Jetzt für €41,65/Monat upgraden';
|
||||
} else {
|
||||
// Show normal price
|
||||
trialBanner.querySelector('h3').textContent = '⚠️ Ihre Trial endet bald!';
|
||||
trialBanner.querySelector('.btn').textContent = 'Jetzt für €49/Monat upgraden';
|
||||
}
|
||||
}
|
||||
|
||||
// Start countdown timer
|
||||
startOfferCountdown();
|
||||
}
|
||||
|
||||
function startOfferCountdown() {
|
||||
const countdownEl = document.getElementById('offerCountdown');
|
||||
if (!countdownEl) return;
|
||||
|
||||
// Set countdown to 48 hours from now (for demo)
|
||||
let timeRemaining = 48 * 60 * 60; // 48 hours in seconds
|
||||
|
||||
const updateCountdown = () => {
|
||||
const hours = Math.floor(timeRemaining / 3600);
|
||||
const minutes = Math.floor((timeRemaining % 3600) / 60);
|
||||
const seconds = timeRemaining % 60;
|
||||
|
||||
countdownEl.textContent = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
||||
|
||||
if (timeRemaining > 0) {
|
||||
timeRemaining--;
|
||||
setTimeout(updateCountdown, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
updateCountdown();
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// File Upload
|
||||
// ============================================
|
||||
function initFileUpload() {
|
||||
const uploadArea = document.getElementById('uploadArea');
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
|
||||
if (!uploadArea || !fileInput) return;
|
||||
|
||||
// Drag and drop
|
||||
uploadArea.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.classList.add('dragover');
|
||||
});
|
||||
|
||||
uploadArea.addEventListener('dragleave', () => {
|
||||
uploadArea.classList.remove('dragover');
|
||||
});
|
||||
|
||||
uploadArea.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.classList.remove('dragover');
|
||||
|
||||
const files = e.dataTransfer.files;
|
||||
handleFiles(files);
|
||||
});
|
||||
|
||||
// File input change
|
||||
fileInput.addEventListener('change', () => {
|
||||
handleFiles(fileInput.files);
|
||||
});
|
||||
}
|
||||
|
||||
function handleFiles(files) {
|
||||
const validFiles = Array.from(files).filter(file => {
|
||||
if (file.type !== 'application/pdf') {
|
||||
showNotification('Nur PDF-Dateien werden unterstützt', 'error');
|
||||
return false;
|
||||
}
|
||||
if (file.size > 10 * 1024 * 1024) { // 10MB
|
||||
showNotification(`${file.name} ist zu groß (max. 10MB)`, 'error');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (validFiles.length === 0) return;
|
||||
|
||||
validFiles.forEach(file => uploadFile(file));
|
||||
}
|
||||
|
||||
async function uploadFile(file) {
|
||||
const uploadProgress = document.getElementById('uploadProgress');
|
||||
const progressFill = document.getElementById('progressFill');
|
||||
const uploadPercent = document.getElementById('uploadPercent');
|
||||
|
||||
// Show progress
|
||||
if (uploadProgress) uploadProgress.style.display = 'block';
|
||||
|
||||
try {
|
||||
// Simulate upload progress (replace with actual upload)
|
||||
for (let i = 0; i <= 100; i += 10) {
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
if (progressFill) progressFill.style.width = `${i}%`;
|
||||
if (uploadPercent) uploadPercent.textContent = `${i}%`;
|
||||
}
|
||||
|
||||
// Add document to list
|
||||
addDocumentToList({
|
||||
name: file.name,
|
||||
size: formatFileSize(file.size),
|
||||
date: new Date().toLocaleDateString('de-DE'),
|
||||
status: 'processing'
|
||||
});
|
||||
|
||||
showNotification(`${file.name} wurde hochgeladen`, 'success');
|
||||
|
||||
// Simulate processing
|
||||
setTimeout(() => {
|
||||
updateDocumentStatus(file.name, 'indexed');
|
||||
updateDocCount();
|
||||
}, 3000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Upload error:', error);
|
||||
showNotification('Fehler beim Hochladen', 'error');
|
||||
} finally {
|
||||
// Hide progress
|
||||
setTimeout(() => {
|
||||
if (uploadProgress) uploadProgress.style.display = 'none';
|
||||
if (progressFill) progressFill.style.width = '0%';
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
function addDocumentToList(doc) {
|
||||
const documentsTable = document.getElementById('documentsTable');
|
||||
if (!documentsTable) return;
|
||||
|
||||
// Remove empty state if present
|
||||
const emptyState = documentsTable.querySelector('.empty-state');
|
||||
if (emptyState) emptyState.remove();
|
||||
|
||||
const docRow = document.createElement('div');
|
||||
docRow.className = 'document-row';
|
||||
docRow.dataset.name = doc.name;
|
||||
docRow.innerHTML = `
|
||||
<div class="doc-icon">
|
||||
<i class="fas fa-file-pdf"></i>
|
||||
</div>
|
||||
<div class="doc-info">
|
||||
<div class="doc-name">${doc.name}</div>
|
||||
<div class="doc-meta">${doc.size} • ${doc.date}</div>
|
||||
</div>
|
||||
<span class="doc-status ${doc.status}">${doc.status === 'indexed' ? 'Indexiert' : 'Verarbeitung...'}</span>
|
||||
<div class="doc-actions">
|
||||
<button title="Herunterladen"><i class="fas fa-download"></i></button>
|
||||
<button class="delete" title="Löschen" onclick="deleteDocument('${doc.name}')"><i class="fas fa-trash"></i></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
documentsTable.appendChild(docRow);
|
||||
}
|
||||
|
||||
function updateDocumentStatus(name, status) {
|
||||
const docRow = document.querySelector(`.document-row[data-name="${name}"]`);
|
||||
if (!docRow) return;
|
||||
|
||||
const statusEl = docRow.querySelector('.doc-status');
|
||||
if (statusEl) {
|
||||
statusEl.className = `doc-status ${status}`;
|
||||
statusEl.textContent = status === 'indexed' ? 'Indexiert' : 'Verarbeitung...';
|
||||
}
|
||||
}
|
||||
|
||||
function deleteDocument(name) {
|
||||
if (!confirm(`Möchten Sie "${name}" wirklich löschen?`)) return;
|
||||
|
||||
const docRow = document.querySelector(`.document-row[data-name="${name}"]`);
|
||||
if (docRow) {
|
||||
docRow.remove();
|
||||
updateDocCount();
|
||||
showNotification('Dokument gelöscht', 'success');
|
||||
}
|
||||
|
||||
// Show empty state if no documents
|
||||
const documentsTable = document.getElementById('documentsTable');
|
||||
if (documentsTable && documentsTable.children.length === 0) {
|
||||
documentsTable.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
<h3>Noch keine Dokumente</h3>
|
||||
<p>Laden Sie Ihr erstes PDF hoch, um zu beginnen</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateDocCount() {
|
||||
const documentsTable = document.getElementById('documentsTable');
|
||||
const totalDocsEl = document.getElementById('totalDocs');
|
||||
const docCountEl = document.getElementById('docCount');
|
||||
|
||||
if (!documentsTable) return;
|
||||
|
||||
const count = documentsTable.querySelectorAll('.document-row').length;
|
||||
|
||||
if (totalDocsEl) totalDocsEl.textContent = `${count} Dokument${count !== 1 ? 'e' : ''}`;
|
||||
if (docCountEl) docCountEl.textContent = count;
|
||||
}
|
||||
|
||||
// Make deleteDocument available globally
|
||||
window.deleteDocument = deleteDocument;
|
||||
|
||||
// ============================================
|
||||
// Chatbot
|
||||
// ============================================
|
||||
function initChatbot() {
|
||||
const chatInput = document.getElementById('chatInput');
|
||||
const sendBtn = document.getElementById('sendBtn');
|
||||
const clearBtn = document.getElementById('clearChat');
|
||||
|
||||
if (!chatInput || !sendBtn) return;
|
||||
|
||||
// Send message on button click
|
||||
sendBtn.addEventListener('click', () => sendMessage());
|
||||
|
||||
// Send message on Enter
|
||||
chatInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
|
||||
// Clear chat
|
||||
if (clearBtn) {
|
||||
clearBtn.addEventListener('click', () => {
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
if (chatMessages) {
|
||||
chatMessages.innerHTML = `
|
||||
<div class="message bot">
|
||||
<p>Hallo! Ich bin Ihr KI-Assistent. Wie kann ich Ihnen helfen?</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function sendMessage() {
|
||||
const chatInput = document.getElementById('chatInput');
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
|
||||
if (!chatInput || !chatMessages) return;
|
||||
|
||||
const message = chatInput.value.trim();
|
||||
if (!message) return;
|
||||
|
||||
// Add user message
|
||||
addChatMessage(message, 'user');
|
||||
chatInput.value = '';
|
||||
|
||||
// Show typing indicator
|
||||
const typingId = showTypingIndicator();
|
||||
|
||||
try {
|
||||
// Send to chat webhook (if configured)
|
||||
const webhookUrl = DASHBOARD_CONFIG.customerData?.chatWebhook;
|
||||
|
||||
if (webhookUrl && webhookUrl !== 'https://sb-demo.userman.de/webhook/rag-chat-webhook/chat') {
|
||||
const response = await fetch(webhookUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
chatInput: message,
|
||||
sessionId: getSessionId(),
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
removeTypingIndicator(typingId);
|
||||
addChatMessage(data.output || data.response || 'Keine Antwort erhalten', 'bot');
|
||||
} else {
|
||||
// Demo response
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
removeTypingIndicator(typingId);
|
||||
|
||||
const demoResponses = [
|
||||
'Das ist eine Demo-Antwort. Laden Sie Dokumente hoch, um echte Antworten zu erhalten.',
|
||||
'Ich kann Ihnen helfen, sobald Sie Ihre Wissensdatenbank mit PDFs gefüllt haben.',
|
||||
'Bitte laden Sie zunächst einige Dokumente hoch, damit ich Ihre Fragen beantworten kann.',
|
||||
];
|
||||
|
||||
addChatMessage(demoResponses[Math.floor(Math.random() * demoResponses.length)], 'bot');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Chat error:', error);
|
||||
removeTypingIndicator(typingId);
|
||||
addChatMessage('Entschuldigung, es gab einen Fehler. Bitte versuchen Sie es erneut.', 'bot');
|
||||
}
|
||||
}
|
||||
|
||||
function addChatMessage(text, type) {
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
if (!chatMessages) return;
|
||||
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `message ${type}`;
|
||||
messageDiv.innerHTML = `<p>${escapeHtml(text)}</p>`;
|
||||
|
||||
chatMessages.appendChild(messageDiv);
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
}
|
||||
|
||||
function showTypingIndicator() {
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
if (!chatMessages) return null;
|
||||
|
||||
const id = 'typing-' + Date.now();
|
||||
const typingDiv = document.createElement('div');
|
||||
typingDiv.className = 'message bot typing';
|
||||
typingDiv.id = id;
|
||||
typingDiv.innerHTML = `
|
||||
<span class="typing-indicator">
|
||||
<span></span><span></span><span></span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
chatMessages.appendChild(typingDiv);
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
function removeTypingIndicator(id) {
|
||||
if (!id) return;
|
||||
const typingDiv = document.getElementById(id);
|
||||
if (typingDiv) typingDiv.remove();
|
||||
}
|
||||
|
||||
function getSessionId() {
|
||||
let sessionId = sessionStorage.getItem('chat_session_id');
|
||||
if (!sessionId) {
|
||||
sessionId = 'session-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
|
||||
sessionStorage.setItem('chat_session_id', sessionId);
|
||||
}
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Embed Code
|
||||
// ============================================
|
||||
function copyEmbedCode() {
|
||||
const embedCode = document.getElementById('embedCode');
|
||||
if (!embedCode) return;
|
||||
|
||||
const text = embedCode.textContent;
|
||||
copyToClipboard('embedCode');
|
||||
|
||||
// Show success message
|
||||
const copySuccess = document.getElementById('copySuccess');
|
||||
if (copySuccess) {
|
||||
copySuccess.style.display = 'flex';
|
||||
setTimeout(() => {
|
||||
copySuccess.style.display = 'none';
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
function copyToClipboard(elementId) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) return;
|
||||
|
||||
const text = element.textContent;
|
||||
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
showNotification('In Zwischenablage kopiert', 'success');
|
||||
}).catch(err => {
|
||||
console.error('Copy failed:', err);
|
||||
// Fallback
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
showNotification('In Zwischenablage kopiert', 'success');
|
||||
});
|
||||
}
|
||||
|
||||
// Make functions available globally
|
||||
window.copyEmbedCode = copyEmbedCode;
|
||||
window.copyToClipboard = copyToClipboard;
|
||||
|
||||
// ============================================
|
||||
// Settings
|
||||
// ============================================
|
||||
function saveSettings() {
|
||||
const primaryColor = document.getElementById('primaryColor')?.value;
|
||||
const botName = document.getElementById('botName')?.value;
|
||||
const welcomeMessage = document.getElementById('welcomeMessage')?.value;
|
||||
|
||||
// Save to localStorage (in production, save to API)
|
||||
const settings = {
|
||||
primaryColor,
|
||||
botName,
|
||||
welcomeMessage,
|
||||
};
|
||||
|
||||
localStorage.setItem('botkonzept_settings', JSON.stringify(settings));
|
||||
showNotification('Einstellungen gespeichert', 'success');
|
||||
}
|
||||
|
||||
// Make saveSettings available globally
|
||||
window.saveSettings = saveSettings;
|
||||
|
||||
// ============================================
|
||||
// Utility Functions
|
||||
// ============================================
|
||||
function showNotification(message, type = 'info') {
|
||||
// Create notification element
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification ${type}`;
|
||||
notification.innerHTML = `
|
||||
<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}"></i>
|
||||
<span>${message}</span>
|
||||
`;
|
||||
|
||||
// Add styles
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
padding: 16px 24px;
|
||||
background: ${type === 'success' ? '#10b981' : type === 'error' ? '#ef4444' : '#6366f1'};
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1);
|
||||
z-index: 9999;
|
||||
animation: slideIn 0.3s ease;
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Remove after 3 seconds
|
||||
setTimeout(() => {
|
||||
notification.style.animation = 'slideOut 0.3s ease';
|
||||
setTimeout(() => notification.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function formatDate(date) {
|
||||
return new Intl.DateTimeFormat('de-DE', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric'
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Add notification animations
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideOut {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
468
js/main.js
Normal file
468
js/main.js
Normal file
@@ -0,0 +1,468 @@
|
||||
/**
|
||||
* BotKonzept - Main JavaScript
|
||||
* ============================
|
||||
* Handles all interactive functionality for the BotKonzept website
|
||||
*/
|
||||
|
||||
// Configuration
|
||||
const CONFIG = {
|
||||
// API Endpoints - Update these for production
|
||||
WEBHOOK_URL: 'https://n8n.userman.de/webhook/botkonzept-registration',
|
||||
API_BASE_URL: 'https://api.botkonzept.de',
|
||||
|
||||
// Validation
|
||||
MIN_NAME_LENGTH: 2,
|
||||
MAX_NAME_LENGTH: 50,
|
||||
|
||||
// Animation
|
||||
SCROLL_OFFSET: 100,
|
||||
ANIMATION_DELAY: 100,
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// DOM Ready
|
||||
// ============================================
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initNavigation();
|
||||
initMobileMenu();
|
||||
initSmoothScroll();
|
||||
initFAQ();
|
||||
initRegistrationForm();
|
||||
initScrollAnimations();
|
||||
initTypingAnimation();
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Navigation
|
||||
// ============================================
|
||||
function initNavigation() {
|
||||
const navbar = document.getElementById('navbar');
|
||||
let lastScroll = 0;
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const currentScroll = window.pageYOffset;
|
||||
|
||||
// Add shadow on scroll
|
||||
if (currentScroll > 50) {
|
||||
navbar.style.boxShadow = '0 4px 6px -1px rgb(0 0 0 / 0.1)';
|
||||
} else {
|
||||
navbar.style.boxShadow = 'none';
|
||||
}
|
||||
|
||||
// Hide/show navbar on scroll (optional)
|
||||
// if (currentScroll > lastScroll && currentScroll > 200) {
|
||||
// navbar.style.transform = 'translateY(-100%)';
|
||||
// } else {
|
||||
// navbar.style.transform = 'translateY(0)';
|
||||
// }
|
||||
|
||||
lastScroll = currentScroll;
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Mobile Menu
|
||||
// ============================================
|
||||
function initMobileMenu() {
|
||||
const mobileMenuBtn = document.getElementById('mobileMenuBtn');
|
||||
const navLinks = document.getElementById('navLinks');
|
||||
|
||||
if (!mobileMenuBtn || !navLinks) return;
|
||||
|
||||
mobileMenuBtn.addEventListener('click', () => {
|
||||
navLinks.classList.toggle('active');
|
||||
|
||||
// Toggle icon
|
||||
const icon = mobileMenuBtn.querySelector('i');
|
||||
if (navLinks.classList.contains('active')) {
|
||||
icon.classList.remove('fa-bars');
|
||||
icon.classList.add('fa-times');
|
||||
} else {
|
||||
icon.classList.remove('fa-times');
|
||||
icon.classList.add('fa-bars');
|
||||
}
|
||||
});
|
||||
|
||||
// Close menu when clicking a link
|
||||
navLinks.querySelectorAll('a').forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
navLinks.classList.remove('active');
|
||||
const icon = mobileMenuBtn.querySelector('i');
|
||||
icon.classList.remove('fa-times');
|
||||
icon.classList.add('fa-bars');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Smooth Scroll
|
||||
// ============================================
|
||||
function initSmoothScroll() {
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const targetId = this.getAttribute('href');
|
||||
|
||||
if (targetId === '#') return;
|
||||
|
||||
const targetElement = document.querySelector(targetId);
|
||||
if (targetElement) {
|
||||
const navbarHeight = document.getElementById('navbar').offsetHeight;
|
||||
const targetPosition = targetElement.offsetTop - navbarHeight - 20;
|
||||
|
||||
window.scrollTo({
|
||||
top: targetPosition,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// FAQ Accordion
|
||||
// ============================================
|
||||
function initFAQ() {
|
||||
const faqItems = document.querySelectorAll('.faq-item');
|
||||
|
||||
faqItems.forEach(item => {
|
||||
const question = item.querySelector('.faq-question');
|
||||
|
||||
question.addEventListener('click', () => {
|
||||
// Close other items
|
||||
faqItems.forEach(otherItem => {
|
||||
if (otherItem !== item && otherItem.classList.contains('active')) {
|
||||
otherItem.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle current item
|
||||
item.classList.toggle('active');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Registration Form
|
||||
// ============================================
|
||||
function initRegistrationForm() {
|
||||
const form = document.getElementById('registerForm');
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
const formSuccess = document.getElementById('formSuccess');
|
||||
const formError = document.getElementById('formError');
|
||||
|
||||
if (!form) return;
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Validate form
|
||||
if (!validateForm(form)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
setLoadingState(submitBtn, true);
|
||||
|
||||
// Collect form data
|
||||
const formData = {
|
||||
firstName: form.firstName.value.trim(),
|
||||
lastName: form.lastName.value.trim(),
|
||||
email: form.email.value.trim(),
|
||||
company: form.company.value.trim() || null,
|
||||
website: form.website.value.trim() || null,
|
||||
newsletter: form.newsletter.checked,
|
||||
timestamp: new Date().toISOString(),
|
||||
source: 'website',
|
||||
userAgent: navigator.userAgent,
|
||||
language: navigator.language,
|
||||
};
|
||||
|
||||
try {
|
||||
// Send registration request
|
||||
const response = await fetch(CONFIG.WEBHOOK_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// Show success message
|
||||
form.style.display = 'none';
|
||||
formSuccess.style.display = 'block';
|
||||
|
||||
// Track conversion (if analytics is set up)
|
||||
trackConversion('registration_complete', formData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
|
||||
// Show error message
|
||||
form.style.display = 'none';
|
||||
formError.style.display = 'block';
|
||||
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
if (errorMessage) {
|
||||
errorMessage.textContent = getErrorMessage(error);
|
||||
}
|
||||
} finally {
|
||||
setLoadingState(submitBtn, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Form validation
|
||||
function validateForm(form) {
|
||||
let isValid = true;
|
||||
|
||||
// Clear previous errors
|
||||
form.querySelectorAll('.error').forEach(el => el.classList.remove('error'));
|
||||
form.querySelectorAll('.error-message').forEach(el => el.remove());
|
||||
|
||||
// Validate first name
|
||||
const firstName = form.firstName.value.trim();
|
||||
if (firstName.length < CONFIG.MIN_NAME_LENGTH) {
|
||||
showFieldError(form.firstName, 'Bitte geben Sie Ihren Vornamen ein');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Validate last name
|
||||
const lastName = form.lastName.value.trim();
|
||||
if (lastName.length < CONFIG.MIN_NAME_LENGTH) {
|
||||
showFieldError(form.lastName, 'Bitte geben Sie Ihren Nachnamen ein');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Validate email
|
||||
const email = form.email.value.trim();
|
||||
if (!isValidEmail(email)) {
|
||||
showFieldError(form.email, 'Bitte geben Sie eine gültige E-Mail-Adresse ein');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Validate website (if provided)
|
||||
const website = form.website.value.trim();
|
||||
if (website && !isValidURL(website)) {
|
||||
showFieldError(form.website, 'Bitte geben Sie eine gültige URL ein (z.B. https://beispiel.de)');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Validate privacy checkbox
|
||||
if (!form.privacy.checked) {
|
||||
showFieldError(form.privacy, 'Bitte akzeptieren Sie die Datenschutzerklärung');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Show field error
|
||||
function showFieldError(field, message) {
|
||||
field.classList.add('error');
|
||||
field.style.borderColor = '#ef4444';
|
||||
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'error-message';
|
||||
errorDiv.style.color = '#ef4444';
|
||||
errorDiv.style.fontSize = '0.75rem';
|
||||
errorDiv.style.marginTop = '4px';
|
||||
errorDiv.textContent = message;
|
||||
|
||||
field.parentNode.appendChild(errorDiv);
|
||||
}
|
||||
|
||||
// Email validation
|
||||
function isValidEmail(email) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
// URL validation
|
||||
function isValidURL(url) {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set loading state
|
||||
function setLoadingState(button, isLoading) {
|
||||
const btnText = button.querySelector('.btn-text');
|
||||
const btnLoading = button.querySelector('.btn-loading');
|
||||
|
||||
if (isLoading) {
|
||||
button.disabled = true;
|
||||
if (btnText) btnText.style.display = 'none';
|
||||
if (btnLoading) btnLoading.style.display = 'inline-flex';
|
||||
} else {
|
||||
button.disabled = false;
|
||||
if (btnText) btnText.style.display = 'inline';
|
||||
if (btnLoading) btnLoading.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Get user-friendly error message
|
||||
function getErrorMessage(error) {
|
||||
if (error.message.includes('Failed to fetch')) {
|
||||
return 'Verbindungsfehler. Bitte überprüfen Sie Ihre Internetverbindung.';
|
||||
}
|
||||
if (error.message.includes('500')) {
|
||||
return 'Serverfehler. Bitte versuchen Sie es später erneut.';
|
||||
}
|
||||
if (error.message.includes('400')) {
|
||||
return 'Ungültige Eingabe. Bitte überprüfen Sie Ihre Daten.';
|
||||
}
|
||||
return 'Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.';
|
||||
}
|
||||
|
||||
// Reset form
|
||||
function resetForm() {
|
||||
const form = document.getElementById('registerForm');
|
||||
const formSuccess = document.getElementById('formSuccess');
|
||||
const formError = document.getElementById('formError');
|
||||
|
||||
if (form) {
|
||||
form.reset();
|
||||
form.style.display = 'flex';
|
||||
}
|
||||
if (formSuccess) formSuccess.style.display = 'none';
|
||||
if (formError) formError.style.display = 'none';
|
||||
}
|
||||
|
||||
// Make resetForm available globally
|
||||
window.resetForm = resetForm;
|
||||
|
||||
// ============================================
|
||||
// Scroll Animations
|
||||
// ============================================
|
||||
function initScrollAnimations() {
|
||||
const animatedElements = document.querySelectorAll('.feature-card, .step, .pricing-card, .faq-item');
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry, index) => {
|
||||
if (entry.isIntersecting) {
|
||||
setTimeout(() => {
|
||||
entry.target.classList.add('visible');
|
||||
entry.target.style.opacity = '1';
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}, index * CONFIG.ANIMATION_DELAY);
|
||||
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.1,
|
||||
rootMargin: '0px 0px -50px 0px'
|
||||
});
|
||||
|
||||
animatedElements.forEach(el => {
|
||||
el.style.opacity = '0';
|
||||
el.style.transform = 'translateY(20px)';
|
||||
el.style.transition = 'opacity 0.5s ease, transform 0.5s ease';
|
||||
observer.observe(el);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Typing Animation (Chat Preview)
|
||||
// ============================================
|
||||
function initTypingAnimation() {
|
||||
const typingMessage = document.querySelector('.message.typing');
|
||||
if (!typingMessage) return;
|
||||
|
||||
// Simulate typing response
|
||||
setTimeout(() => {
|
||||
typingMessage.innerHTML = '<p>Sie können Ihre Bestellung ganz einfach über unser Online-Formular aufgeben. Alternativ können Sie uns auch telefonisch unter 0800-123456 erreichen.</p>';
|
||||
typingMessage.classList.remove('typing');
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Analytics & Tracking
|
||||
// ============================================
|
||||
function trackConversion(eventName, data = {}) {
|
||||
// Google Analytics 4
|
||||
if (typeof gtag !== 'undefined') {
|
||||
gtag('event', eventName, {
|
||||
'event_category': 'conversion',
|
||||
'event_label': 'registration',
|
||||
...data
|
||||
});
|
||||
}
|
||||
|
||||
// Facebook Pixel
|
||||
if (typeof fbq !== 'undefined') {
|
||||
fbq('track', 'Lead', data);
|
||||
}
|
||||
|
||||
// Console log for debugging
|
||||
console.log('Conversion tracked:', eventName, data);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Utility Functions
|
||||
// ============================================
|
||||
|
||||
// Debounce function
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
// Throttle function
|
||||
function throttle(func, limit) {
|
||||
let inThrottle;
|
||||
return function(...args) {
|
||||
if (!inThrottle) {
|
||||
func.apply(this, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Format currency
|
||||
function formatCurrency(amount, currency = 'EUR') {
|
||||
return new Intl.NumberFormat('de-DE', {
|
||||
style: 'currency',
|
||||
currency: currency
|
||||
}).format(amount);
|
||||
}
|
||||
|
||||
// Format date
|
||||
function formatDate(date) {
|
||||
return new Intl.DateTimeFormat('de-DE', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
}).format(new Date(date));
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Export for testing
|
||||
// ============================================
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = {
|
||||
validateForm,
|
||||
isValidEmail,
|
||||
isValidURL,
|
||||
formatCurrency,
|
||||
formatDate
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user