cookie added, optimisation
This commit is contained in:
642
Profice WebSite/scripts/add/send.php
Normal file
642
Profice WebSite/scripts/add/send.php
Normal file
@@ -0,0 +1,642 @@
|
||||
<?php
|
||||
/**
|
||||
* Profice Web API Handler
|
||||
* Centralized API endpoint for all form submissions, webhooks, and tracking
|
||||
* ALL SENSITIVE DATA IS STORED HERE - NOT VISIBLE TO CLIENT
|
||||
*
|
||||
* @author Profice Development Team
|
||||
* @version 2.0.0
|
||||
*/
|
||||
|
||||
// ==========================================
|
||||
// SECURITY HEADERS
|
||||
// ==========================================
|
||||
header('Content-Type: application/json');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
header('X-Frame-Options: DENY');
|
||||
header('X-XSS-Protection: 1; mode=block');
|
||||
header('Referrer-Policy: strict-origin-when-cross-origin');
|
||||
|
||||
// CORS - Restrict to your domain in production
|
||||
$allowedOrigins = [
|
||||
'https://profice.de',
|
||||
'https://www.profice.de',
|
||||
'http://localhost',
|
||||
'http://127.0.0.1'
|
||||
];
|
||||
|
||||
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||
if (in_array($origin, $allowedOrigins)) {
|
||||
header("Access-Control-Allow-Origin: $origin");
|
||||
} else {
|
||||
header('Access-Control-Allow-Origin: *'); // Development fallback
|
||||
}
|
||||
|
||||
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
header('Access-Control-Max-Age: 86400');
|
||||
|
||||
// Handle preflight OPTIONS request
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit();
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// SENSITIVE CONFIGURATION - HIDDEN FROM CLIENT
|
||||
// ==========================================
|
||||
|
||||
// Environment
|
||||
define('USE_PRODUCTION', true);
|
||||
define('DEBUG_MODE', false);
|
||||
|
||||
// N8N Webhooks
|
||||
define('WEBHOOK_TEST', 'https://n8n.profice.de/webhook-test/d94ef798-3f43-46dd-8207-1e335e64518f');
|
||||
define('WEBHOOK_PROD', 'https://n8n.profice.de/webhook/d94ef798-3f43-46dd-8207-1e335e64518f');
|
||||
define('WEBHOOK_URL', USE_PRODUCTION ? WEBHOOK_PROD : WEBHOOK_TEST);
|
||||
|
||||
// Google Analytics
|
||||
define('GA_MEASUREMENT_ID', 'G-XXXXXXXXXX'); // Replace with your actual ID
|
||||
define('GA_API_SECRET', ''); // For server-side tracking
|
||||
|
||||
// Google Tag Manager
|
||||
define('GTM_CONTAINER_ID', 'GTM-XXXXXXX'); // Replace with your actual ID
|
||||
|
||||
// Facebook Pixel
|
||||
define('FB_PIXEL_ID', ''); // Replace with your actual ID
|
||||
define('FB_ACCESS_TOKEN', ''); // For Conversions API
|
||||
|
||||
// Google Ads
|
||||
define('GADS_CONVERSION_ID', 'AW-XXXXXXXXXX');
|
||||
define('GADS_CONVERSION_LABEL', '');
|
||||
|
||||
// LinkedIn Insight
|
||||
define('LINKEDIN_PARTNER_ID', '');
|
||||
|
||||
// API Keys
|
||||
define('API_SECRET_KEY', 'your-secret-key-here'); // For API authentication
|
||||
|
||||
// Rate Limiting
|
||||
define('RATE_LIMIT_REQUESTS', 100);
|
||||
define('RATE_LIMIT_WINDOW', 3600); // 1 hour
|
||||
|
||||
// ==========================================
|
||||
// HELPER FUNCTIONS
|
||||
// ==========================================
|
||||
|
||||
function sendResponse($success, $message, $data = null, $statusCode = 200) {
|
||||
http_response_code($statusCode);
|
||||
$response = [
|
||||
'success' => $success,
|
||||
'message' => $message,
|
||||
'timestamp' => date('c')
|
||||
];
|
||||
|
||||
// Only include data if not null and not in production (security)
|
||||
if ($data !== null && (!USE_PRODUCTION || DEBUG_MODE)) {
|
||||
$response['data'] = $data;
|
||||
} elseif ($data !== null && $success) {
|
||||
// In production, only return safe data
|
||||
$response['data'] = filterSafeData($data);
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
exit();
|
||||
}
|
||||
|
||||
function filterSafeData($data) {
|
||||
// Remove sensitive fields from response
|
||||
$sensitiveFields = ['webhook_url', 'ip_address', 'user_agent', 'http_code', 'error'];
|
||||
|
||||
if (is_array($data)) {
|
||||
foreach ($sensitiveFields as $field) {
|
||||
unset($data[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
function getClientIP() {
|
||||
$ipKeys = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'];
|
||||
|
||||
foreach ($ipKeys as $key) {
|
||||
if (array_key_exists($key, $_SERVER)) {
|
||||
foreach (explode(',', $_SERVER[$key]) as $ip) {
|
||||
$ip = trim($ip);
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
|
||||
return $ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||
}
|
||||
|
||||
function sanitizeInput($input) {
|
||||
if (is_array($input)) {
|
||||
return array_map('sanitizeInput', $input);
|
||||
}
|
||||
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
function validateCSRFToken($token) {
|
||||
// Implement CSRF validation if needed
|
||||
return true;
|
||||
}
|
||||
|
||||
function checkRateLimit($ip) {
|
||||
$rateLimitFile = __DIR__ . '/rate_limits.json';
|
||||
$limits = [];
|
||||
|
||||
if (file_exists($rateLimitFile)) {
|
||||
$limits = json_decode(file_get_contents($rateLimitFile), true) ?: [];
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$windowStart = $now - RATE_LIMIT_WINDOW;
|
||||
|
||||
// Clean old entries
|
||||
$limits = array_filter($limits, function($entry) use ($windowStart) {
|
||||
return $entry['time'] > $windowStart;
|
||||
});
|
||||
|
||||
// Count requests from this IP
|
||||
$ipRequests = array_filter($limits, function($entry) use ($ip) {
|
||||
return $entry['ip'] === $ip;
|
||||
});
|
||||
|
||||
if (count($ipRequests) >= RATE_LIMIT_REQUESTS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add new request
|
||||
$limits[] = ['ip' => $ip, 'time' => $now];
|
||||
file_put_contents($rateLimitFile, json_encode($limits));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// WEBHOOK FUNCTIONS
|
||||
// ==========================================
|
||||
|
||||
function sendToWebhook($data, $webhookUrl = null) {
|
||||
$url = $webhookUrl ?? WEBHOOK_URL;
|
||||
|
||||
$ch = curl_init();
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => json_encode($data),
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/json',
|
||||
'User-Agent: Profice-Web-API/2.0'
|
||||
],
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
if ($error) {
|
||||
error_log("Webhook Error: " . $error);
|
||||
return ['success' => false, 'error' => $error];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => $httpCode >= 200 && $httpCode < 300,
|
||||
'http_code' => $httpCode
|
||||
];
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// TRACKING FUNCTIONS - SERVER SIDE
|
||||
// ==========================================
|
||||
|
||||
function getTrackingConfig() {
|
||||
// Return tracking configuration for client
|
||||
// IDs are loaded from server, not exposed in JS
|
||||
return [
|
||||
'analytics_enabled' => !empty(GA_MEASUREMENT_ID) && GA_MEASUREMENT_ID !== 'G-XXXXXXXXXX',
|
||||
'gtm_enabled' => !empty(GTM_CONTAINER_ID) && GTM_CONTAINER_ID !== 'GTM-XXXXXXX',
|
||||
'fb_enabled' => !empty(FB_PIXEL_ID),
|
||||
'gads_enabled' => !empty(GADS_CONVERSION_ID) && GADS_CONVERSION_ID !== 'AW-XXXXXXXXXX',
|
||||
'linkedin_enabled' => !empty(LINKEDIN_PARTNER_ID)
|
||||
];
|
||||
}
|
||||
|
||||
function loadTrackingScripts($preferences) {
|
||||
$scripts = [];
|
||||
|
||||
// Google Analytics
|
||||
if ($preferences['analytics'] && !empty(GA_MEASUREMENT_ID) && GA_MEASUREMENT_ID !== 'G-XXXXXXXXXX') {
|
||||
$scripts['ga'] = [
|
||||
'type' => 'analytics',
|
||||
'src' => 'https://www.googletagmanager.com/gtag/js?id=' . GA_MEASUREMENT_ID,
|
||||
'config' => [
|
||||
'id' => GA_MEASUREMENT_ID
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
// Google Tag Manager
|
||||
if ($preferences['analytics'] && !empty(GTM_CONTAINER_ID) && GTM_CONTAINER_ID !== 'GTM-XXXXXXX') {
|
||||
$scripts['gtm'] = [
|
||||
'type' => 'analytics',
|
||||
'id' => GTM_CONTAINER_ID
|
||||
];
|
||||
}
|
||||
|
||||
// Facebook Pixel
|
||||
if ($preferences['marketing'] && !empty(FB_PIXEL_ID)) {
|
||||
$scripts['fb'] = [
|
||||
'type' => 'marketing',
|
||||
'id' => FB_PIXEL_ID
|
||||
];
|
||||
}
|
||||
|
||||
// Google Ads
|
||||
if ($preferences['marketing'] && !empty(GADS_CONVERSION_ID) && GADS_CONVERSION_ID !== 'AW-XXXXXXXXXX') {
|
||||
$scripts['gads'] = [
|
||||
'type' => 'marketing',
|
||||
'id' => GADS_CONVERSION_ID
|
||||
];
|
||||
}
|
||||
|
||||
// LinkedIn
|
||||
if ($preferences['marketing'] && !empty(LINKEDIN_PARTNER_ID)) {
|
||||
$scripts['linkedin'] = [
|
||||
'type' => 'marketing',
|
||||
'id' => LINKEDIN_PARTNER_ID
|
||||
];
|
||||
}
|
||||
|
||||
return $scripts;
|
||||
}
|
||||
|
||||
function trackEvent($eventName, $eventData, $preferences) {
|
||||
$results = [];
|
||||
|
||||
// Server-side Google Analytics tracking
|
||||
if ($preferences['analytics'] && !empty(GA_MEASUREMENT_ID) && !empty(GA_API_SECRET)) {
|
||||
$results['ga'] = sendGAEvent($eventName, $eventData);
|
||||
}
|
||||
|
||||
// Server-side Facebook Conversions API
|
||||
if ($preferences['marketing'] && !empty(FB_PIXEL_ID) && !empty(FB_ACCESS_TOKEN)) {
|
||||
$results['fb'] = sendFBEvent($eventName, $eventData);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
function sendGAEvent($eventName, $eventData) {
|
||||
if (empty(GA_API_SECRET)) return ['success' => false, 'error' => 'No API secret'];
|
||||
|
||||
$url = 'https://www.google-analytics.com/mp/collect?measurement_id=' . GA_MEASUREMENT_ID . '&api_secret=' . GA_API_SECRET;
|
||||
|
||||
$payload = [
|
||||
'client_id' => $eventData['client_id'] ?? uniqid(),
|
||||
'events' => [[
|
||||
'name' => $eventName,
|
||||
'params' => $eventData
|
||||
]]
|
||||
];
|
||||
|
||||
return sendToWebhook($payload, $url);
|
||||
}
|
||||
|
||||
function sendFBEvent($eventName, $eventData) {
|
||||
if (empty(FB_ACCESS_TOKEN)) return ['success' => false, 'error' => 'No access token'];
|
||||
|
||||
$url = 'https://graph.facebook.com/v18.0/' . FB_PIXEL_ID . '/events?access_token=' . FB_ACCESS_TOKEN;
|
||||
|
||||
$payload = [
|
||||
'data' => [[
|
||||
'event_name' => $eventName,
|
||||
'event_time' => time(),
|
||||
'action_source' => 'website',
|
||||
'user_data' => [
|
||||
'client_ip_address' => getClientIP(),
|
||||
'client_user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
|
||||
],
|
||||
'custom_data' => $eventData
|
||||
]]
|
||||
];
|
||||
|
||||
return sendToWebhook($payload, $url);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// STORAGE FUNCTIONS
|
||||
// ==========================================
|
||||
|
||||
function storeLead($formData) {
|
||||
$leadFile = __DIR__ . '/data/leads.json';
|
||||
$dir = dirname($leadFile);
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
|
||||
$leads = [];
|
||||
if (file_exists($leadFile)) {
|
||||
$leads = json_decode(file_get_contents($leadFile), true) ?: [];
|
||||
}
|
||||
|
||||
$lead = [
|
||||
'id' => uniqid('lead_'),
|
||||
'datum' => date('d.m.Y'),
|
||||
'dienstleistung' => $formData['service'] ?? 'Allgemein',
|
||||
'status' => 'open',
|
||||
'statusText' => 'Offen',
|
||||
'description' => $formData['description'] ?? '',
|
||||
'name' => $formData['name'] ?? '',
|
||||
'contact' => $formData['contact'] ?? '',
|
||||
'organisation' => $formData['organisation'] ?? '',
|
||||
'timestamp' => date('c')
|
||||
];
|
||||
|
||||
array_unshift($leads, $lead);
|
||||
$leads = array_slice($leads, 0, 100);
|
||||
|
||||
file_put_contents($leadFile, json_encode($leads, JSON_PRETTY_PRINT));
|
||||
return $lead['id'];
|
||||
}
|
||||
|
||||
function storeCookieConsent($consentData) {
|
||||
$consentFile = __DIR__ . '/data/cookie_consents.json';
|
||||
$dir = dirname($consentFile);
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
|
||||
$consents = [];
|
||||
if (file_exists($consentFile)) {
|
||||
$consents = json_decode(file_get_contents($consentFile), true) ?: [];
|
||||
}
|
||||
|
||||
// Remove existing consent for this IP
|
||||
$ip = getClientIP();
|
||||
$consents = array_filter($consents, function($c) use ($ip) {
|
||||
return ($c['ip_address'] ?? '') !== $ip;
|
||||
});
|
||||
|
||||
$consentData['id'] = uniqid('consent_');
|
||||
$consentData['ip_address'] = $ip;
|
||||
$consentData['timestamp'] = date('c');
|
||||
|
||||
array_unshift($consents, $consentData);
|
||||
$consents = array_slice($consents, 0, 1000);
|
||||
|
||||
file_put_contents($consentFile, json_encode($consents, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
function getCookieConsentByIP() {
|
||||
$consentFile = __DIR__ . '/data/cookie_consents.json';
|
||||
|
||||
if (!file_exists($consentFile)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$consents = json_decode(file_get_contents($consentFile), true) ?: [];
|
||||
$ip = getClientIP();
|
||||
|
||||
foreach ($consents as $consent) {
|
||||
if (($consent['ip_address'] ?? '') === $ip) {
|
||||
return $consent;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// REQUEST HANDLERS
|
||||
// ==========================================
|
||||
|
||||
function handleContactForm($data) {
|
||||
$required = ['name', 'contact'];
|
||||
foreach ($required as $field) {
|
||||
if (empty($data[$field])) {
|
||||
sendResponse(false, "Pflichtfeld fehlt: {$field}", null, 400);
|
||||
}
|
||||
}
|
||||
|
||||
$formData = [
|
||||
'name' => sanitizeInput($data['name']),
|
||||
'organisation' => sanitizeInput($data['organisation'] ?? ''),
|
||||
'contact' => sanitizeInput($data['contact']),
|
||||
'service' => sanitizeInput($data['service'] ?? ''),
|
||||
'budget' => sanitizeInput($data['budget'] ?? ''),
|
||||
'description' => sanitizeInput($data['description'] ?? ''),
|
||||
'timestamp' => date('c'),
|
||||
'source' => 'contact_form',
|
||||
'ip_address' => getClientIP()
|
||||
];
|
||||
|
||||
$webhookResult = sendToWebhook($formData);
|
||||
|
||||
if ($webhookResult['success']) {
|
||||
storeLead($formData);
|
||||
}
|
||||
|
||||
sendResponse(true, 'Formular erfolgreich gesendet');
|
||||
}
|
||||
|
||||
function handleLoginForm($data) {
|
||||
$required = ['email', 'password'];
|
||||
foreach ($required as $field) {
|
||||
if (empty($data[$field])) {
|
||||
sendResponse(false, "Pflichtfeld fehlt: {$field}", null, 400);
|
||||
}
|
||||
}
|
||||
|
||||
$formData = [
|
||||
'email' => sanitizeInput($data['email']),
|
||||
'password' => $data['password'], // Don't sanitize password
|
||||
'remember' => $data['remember'] ?? false,
|
||||
'timestamp' => date('c'),
|
||||
'source' => 'login_form',
|
||||
'ip_address' => getClientIP()
|
||||
];
|
||||
|
||||
$webhookResult = sendToWebhook($formData);
|
||||
|
||||
sendResponse(true, 'Anmeldung verarbeitet');
|
||||
}
|
||||
|
||||
function handleRegisterForm($data) {
|
||||
$required = ['name', 'email', 'password'];
|
||||
foreach ($required as $field) {
|
||||
if (empty($data[$field])) {
|
||||
sendResponse(false, "Pflichtfeld fehlt: {$field}", null, 400);
|
||||
}
|
||||
}
|
||||
|
||||
$formData = [
|
||||
'name' => sanitizeInput($data['name']),
|
||||
'email' => sanitizeInput($data['email']),
|
||||
'password' => $data['password'],
|
||||
'company' => sanitizeInput($data['company'] ?? ''),
|
||||
'phone' => sanitizeInput($data['phone'] ?? ''),
|
||||
'timestamp' => date('c'),
|
||||
'source' => 'register_form',
|
||||
'ip_address' => getClientIP()
|
||||
];
|
||||
|
||||
$webhookResult = sendToWebhook($formData);
|
||||
|
||||
sendResponse(true, 'Registrierung erfolgreich');
|
||||
}
|
||||
|
||||
function handleLeadForm($data) {
|
||||
$formData = [
|
||||
'lead_data' => $data,
|
||||
'timestamp' => date('c'),
|
||||
'source' => 'lead_form',
|
||||
'ip_address' => getClientIP()
|
||||
];
|
||||
|
||||
$webhookResult = sendToWebhook($formData);
|
||||
|
||||
sendResponse(true, 'Lead erfolgreich gesendet');
|
||||
}
|
||||
|
||||
function handleCookieConsent($data) {
|
||||
if (empty($data['preferences'])) {
|
||||
sendResponse(false, 'Preferences fehlen', null, 400);
|
||||
}
|
||||
|
||||
$consentData = [
|
||||
'hasConsented' => true,
|
||||
'preferences' => $data['preferences'],
|
||||
'source' => 'cookie_consent'
|
||||
];
|
||||
|
||||
storeCookieConsent($consentData);
|
||||
|
||||
// Get tracking scripts based on consent
|
||||
$scripts = loadTrackingScripts($data['preferences']);
|
||||
|
||||
// Send consent event to webhook
|
||||
$webhookData = [
|
||||
'event_type' => 'cookie_consent_update',
|
||||
'preferences' => $data['preferences'],
|
||||
'timestamp' => date('c'),
|
||||
'ip_address' => getClientIP()
|
||||
];
|
||||
sendToWebhook($webhookData);
|
||||
|
||||
sendResponse(true, 'Cookie-Einstellungen gespeichert', ['scripts' => $scripts]);
|
||||
}
|
||||
|
||||
function handleGetCookieConsent($data) {
|
||||
$consent = getCookieConsentByIP();
|
||||
|
||||
if ($consent) {
|
||||
// Get tracking scripts based on saved consent
|
||||
$scripts = [];
|
||||
if (!empty($consent['preferences'])) {
|
||||
$scripts = loadTrackingScripts($consent['preferences']);
|
||||
}
|
||||
|
||||
sendResponse(true, 'Consent gefunden', [
|
||||
'hasConsented' => $consent['hasConsented'] ?? false,
|
||||
'preferences' => $consent['preferences'] ?? [],
|
||||
'scripts' => $scripts
|
||||
]);
|
||||
} else {
|
||||
sendResponse(false, 'Kein Consent gefunden', null, 404);
|
||||
}
|
||||
}
|
||||
|
||||
function handleGetTrackingConfig($data) {
|
||||
$config = getTrackingConfig();
|
||||
sendResponse(true, 'Tracking-Konfiguration', $config);
|
||||
}
|
||||
|
||||
function handleTrackEvent($data) {
|
||||
if (empty($data['event_name'])) {
|
||||
sendResponse(false, 'Event name fehlt', null, 400);
|
||||
}
|
||||
|
||||
$preferences = $data['preferences'] ?? ['analytics' => false, 'marketing' => false];
|
||||
$results = trackEvent($data['event_name'], $data['event_data'] ?? [], $preferences);
|
||||
|
||||
sendResponse(true, 'Event getrackt', $results);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// MAIN REQUEST HANDLER
|
||||
// ==========================================
|
||||
|
||||
// Validate request method
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
sendResponse(false, 'Nur POST-Anfragen erlaubt', null, 405);
|
||||
}
|
||||
|
||||
// Rate limiting
|
||||
$clientIP = getClientIP();
|
||||
if (!checkRateLimit($clientIP)) {
|
||||
sendResponse(false, 'Zu viele Anfragen. Bitte später erneut versuchen.', null, 429);
|
||||
}
|
||||
|
||||
// Get JSON input
|
||||
$jsonInput = file_get_contents('php://input');
|
||||
$data = json_decode($jsonInput, true);
|
||||
|
||||
if (!$data) {
|
||||
$data = $_POST;
|
||||
}
|
||||
|
||||
if (!$data || empty($data)) {
|
||||
sendResponse(false, 'Keine Daten empfangen', null, 400);
|
||||
}
|
||||
|
||||
$requestType = $data['type'] ?? 'contact';
|
||||
|
||||
try {
|
||||
switch ($requestType) {
|
||||
case 'contact':
|
||||
handleContactForm($data);
|
||||
break;
|
||||
case 'login':
|
||||
handleLoginForm($data);
|
||||
break;
|
||||
case 'register':
|
||||
handleRegisterForm($data);
|
||||
break;
|
||||
case 'lead':
|
||||
handleLeadForm($data);
|
||||
break;
|
||||
case 'cookie_consent':
|
||||
handleCookieConsent($data);
|
||||
break;
|
||||
case 'get_cookie_consent':
|
||||
handleGetCookieConsent($data);
|
||||
break;
|
||||
case 'get_tracking_config':
|
||||
handleGetTrackingConfig($data);
|
||||
break;
|
||||
case 'track_event':
|
||||
handleTrackEvent($data);
|
||||
break;
|
||||
default:
|
||||
sendResponse(false, 'Ungültiger Anfragetyp', null, 400);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log('API Error: ' . $e->getMessage());
|
||||
sendResponse(false, 'Interner Serverfehler', null, 500);
|
||||
}
|
||||
?>
|
||||
423
Profice WebSite/scripts/cookie-consent.js
Normal file
423
Profice WebSite/scripts/cookie-consent.js
Normal file
@@ -0,0 +1,423 @@
|
||||
/**
|
||||
* Cookie Consent Management System
|
||||
* All sensitive data (tracking IDs, URLs) are loaded from server-side PHP
|
||||
* Includes fallback for local file access (no server)
|
||||
*
|
||||
* @version 2.1.0
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// ==========================================
|
||||
// CONFIGURATION
|
||||
// ==========================================
|
||||
|
||||
const cookieCategories = ['necessary', 'analytics', 'marketing'];
|
||||
const API_ENDPOINT = 'scripts/add/send.php';
|
||||
|
||||
// Detect if running from file:// protocol (no server)
|
||||
const isLocalFile = window.location.protocol === 'file:';
|
||||
|
||||
let consentState = {
|
||||
hasConsented: false,
|
||||
preferences: {}
|
||||
};
|
||||
|
||||
let previousPreferences = {};
|
||||
let trackingScripts = {};
|
||||
|
||||
// ==========================================
|
||||
// DATALAYER INTEGRATION
|
||||
// ==========================================
|
||||
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
function pushConsentToDataLayer(preferences) {
|
||||
window.dataLayer.push({
|
||||
'event': 'consent_update',
|
||||
'consent': {
|
||||
'analytics_storage': preferences.analytics ? 'granted' : 'denied',
|
||||
'ad_storage': preferences.marketing ? 'granted' : 'denied',
|
||||
'functionality_storage': preferences.necessary ? 'granted' : 'denied',
|
||||
'personalization_storage': preferences.marketing ? 'granted' : 'denied',
|
||||
'security_storage': 'granted'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setDefaultConsent() {
|
||||
window.dataLayer.push({
|
||||
'event': 'consent_default',
|
||||
'consent': {
|
||||
'analytics_storage': 'denied',
|
||||
'ad_storage': 'denied',
|
||||
'functionality_storage': 'granted',
|
||||
'personalization_storage': 'denied',
|
||||
'security_storage': 'granted',
|
||||
'wait_for_update': 500
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// STORAGE (API + LOCALSTORAGE FALLBACK)
|
||||
// ==========================================
|
||||
|
||||
async function saveConsentToStorage(preferences) {
|
||||
// Always save to localStorage as backup
|
||||
const consentData = {
|
||||
hasConsented: true,
|
||||
preferences: preferences,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
localStorage.setItem('cookieConsent', JSON.stringify(consentData));
|
||||
|
||||
// Try API if not local file
|
||||
if (!isLocalFile) {
|
||||
try {
|
||||
const response = await fetch(API_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type: 'cookie_consent', preferences })
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success && result.data && result.data.scripts) {
|
||||
trackingScripts = result.data.scripts;
|
||||
}
|
||||
return result.success;
|
||||
} catch (e) {
|
||||
// API failed, localStorage already saved
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function getConsentFromStorage() {
|
||||
// Try localStorage first (faster)
|
||||
const localConsent = localStorage.getItem('cookieConsent');
|
||||
if (localConsent) {
|
||||
try {
|
||||
return JSON.parse(localConsent);
|
||||
} catch (e) {
|
||||
localStorage.removeItem('cookieConsent');
|
||||
}
|
||||
}
|
||||
|
||||
// Try API if not local file
|
||||
if (!isLocalFile) {
|
||||
try {
|
||||
const response = await fetch(API_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type: 'get_cookie_consent' })
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success && result.data) {
|
||||
if (result.data.scripts) {
|
||||
trackingScripts = result.data.scripts;
|
||||
}
|
||||
// Cache in localStorage
|
||||
localStorage.setItem('cookieConsent', JSON.stringify(result.data));
|
||||
return result.data;
|
||||
}
|
||||
} catch (e) {
|
||||
// API unavailable
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// SCRIPT LOADING
|
||||
// ==========================================
|
||||
|
||||
function loadTrackingScripts() {
|
||||
if (!trackingScripts || Object.keys(trackingScripts).length === 0) return;
|
||||
|
||||
if (trackingScripts.ga && consentState.preferences.analytics) {
|
||||
loadGoogleAnalytics(trackingScripts.ga);
|
||||
}
|
||||
if (trackingScripts.gtm && consentState.preferences.analytics) {
|
||||
loadGTM(trackingScripts.gtm);
|
||||
}
|
||||
if (trackingScripts.fb && consentState.preferences.marketing) {
|
||||
loadFacebookPixel(trackingScripts.fb);
|
||||
}
|
||||
if (trackingScripts.gads && consentState.preferences.marketing) {
|
||||
loadGoogleAds(trackingScripts.gads);
|
||||
}
|
||||
if (trackingScripts.linkedin && consentState.preferences.marketing) {
|
||||
loadLinkedIn(trackingScripts.linkedin);
|
||||
}
|
||||
}
|
||||
|
||||
function loadScript(src, callback) {
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.async = true;
|
||||
if (callback) script.onload = callback;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
function loadGoogleAnalytics(config) {
|
||||
if (!config.src || !config.config || !config.config.id) return;
|
||||
loadScript(config.src, function() {
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
window.gtag = gtag;
|
||||
gtag('js', new Date());
|
||||
gtag('config', config.config.id);
|
||||
});
|
||||
}
|
||||
|
||||
function loadGTM(config) {
|
||||
if (!config.id) return;
|
||||
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer',config.id);
|
||||
}
|
||||
|
||||
function loadFacebookPixel(config) {
|
||||
if (!config.id) return;
|
||||
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
||||
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
|
||||
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
|
||||
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
|
||||
document,'script','https://connect.facebook.net/en_US/fbevents.js');
|
||||
fbq('init', config.id);
|
||||
fbq('track', 'PageView');
|
||||
}
|
||||
|
||||
function loadGoogleAds(config) {
|
||||
if (!config.id) return;
|
||||
loadScript('https://www.googletagmanager.com/gtag/js?id=' + config.id, function() {
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', config.id);
|
||||
});
|
||||
}
|
||||
|
||||
function loadLinkedIn(config) {
|
||||
if (!config.id) return;
|
||||
window._linkedin_partner_id = config.id;
|
||||
window._linkedin_data_partner_ids = window._linkedin_data_partner_ids || [];
|
||||
window._linkedin_data_partner_ids.push(config.id);
|
||||
loadScript('https://snap.licdn.com/li.lms-analytics/insight.min.js');
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// COOKIE DELETION
|
||||
// ==========================================
|
||||
|
||||
const cookiePatterns = {
|
||||
analytics: ['_ga', '_gid', '_gat', '__utma', '__utmb', '__utmc', '__utmz'],
|
||||
marketing: ['_fbp', '_fbc', 'fr', '_gcl_au', '_gcl_aw', 'IDE', 'DSID', 'NID']
|
||||
};
|
||||
|
||||
function deleteCookie(name) {
|
||||
const paths = ['/', window.location.pathname];
|
||||
const domains = ['', window.location.hostname, '.' + window.location.hostname];
|
||||
|
||||
paths.forEach(path => {
|
||||
domains.forEach(domain => {
|
||||
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}${domain ? '; domain=' + domain : ''}`;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deleteCookiesForCategory(category) {
|
||||
const patterns = cookiePatterns[category] || [];
|
||||
const allCookies = document.cookie.split(';');
|
||||
|
||||
allCookies.forEach(cookie => {
|
||||
const cookieName = cookie.split('=')[0].trim();
|
||||
patterns.forEach(pattern => {
|
||||
if (cookieName.startsWith(pattern)) {
|
||||
deleteCookie(cookieName);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handlePreferenceChanges(newPreferences) {
|
||||
Object.keys(cookiePatterns).forEach(category => {
|
||||
if (previousPreferences[category] === true && newPreferences[category] === false) {
|
||||
deleteCookiesForCategory(category);
|
||||
window.dataLayer.push({ 'event': 'consent_revoked', 'consent_category': category });
|
||||
}
|
||||
});
|
||||
previousPreferences = { ...newPreferences };
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// DOM ELEMENTS
|
||||
// ==========================================
|
||||
|
||||
const cookieBanner = document.getElementById('cookieBanner');
|
||||
const cookieModal = document.getElementById('cookieModal');
|
||||
const acceptAllBtn = document.getElementById('cookieAcceptAll');
|
||||
const rejectAllBtn = document.getElementById('cookieRejectAll');
|
||||
const settingsBtn = document.getElementById('cookieSettings');
|
||||
const modalCloseBtn = document.getElementById('cookieModalClose');
|
||||
const savePreferencesBtn = document.getElementById('cookieSavePreferences');
|
||||
|
||||
// ==========================================
|
||||
// CORE FUNCTIONS
|
||||
// ==========================================
|
||||
|
||||
async function initCookieConsent() {
|
||||
setDefaultConsent();
|
||||
|
||||
const savedConsent = await getConsentFromStorage();
|
||||
|
||||
if (savedConsent && savedConsent.hasConsented) {
|
||||
consentState = {
|
||||
hasConsented: true,
|
||||
preferences: savedConsent.preferences || {}
|
||||
};
|
||||
previousPreferences = { ...consentState.preferences };
|
||||
applyConsentPreferences();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show banner after short delay
|
||||
setTimeout(() => {
|
||||
if (cookieBanner) cookieBanner.classList.add('show');
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function applyConsentPreferences() {
|
||||
pushConsentToDataLayer(consentState.preferences);
|
||||
loadTrackingScripts();
|
||||
window.dispatchEvent(new CustomEvent('cookieConsentUpdated', { detail: consentState.preferences }));
|
||||
}
|
||||
|
||||
async function saveConsent(preferences) {
|
||||
handlePreferenceChanges(preferences);
|
||||
await saveConsentToStorage(preferences);
|
||||
|
||||
consentState = {
|
||||
hasConsented: true,
|
||||
preferences: preferences,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
applyConsentPreferences();
|
||||
}
|
||||
|
||||
function hideBanner() {
|
||||
if (cookieBanner) cookieBanner.classList.remove('show');
|
||||
}
|
||||
|
||||
function showSettingsModal() {
|
||||
if (cookieModal) {
|
||||
cookieModal.classList.add('show');
|
||||
populateSettingsModal();
|
||||
}
|
||||
}
|
||||
|
||||
function hideSettingsModal() {
|
||||
if (cookieModal) cookieModal.classList.remove('show');
|
||||
}
|
||||
|
||||
function populateSettingsModal() {
|
||||
cookieCategories.forEach(key => {
|
||||
const toggle = document.getElementById(`cookie-toggle-${key}`);
|
||||
if (!toggle) return;
|
||||
|
||||
const isEnabled = consentState.preferences[key] || false;
|
||||
const isRequired = key === 'necessary';
|
||||
|
||||
if (isRequired) {
|
||||
toggle.classList.add('active', 'disabled');
|
||||
toggle.setAttribute('aria-checked', 'true');
|
||||
toggle.setAttribute('aria-disabled', 'true');
|
||||
} else {
|
||||
toggle.classList.toggle('active', isEnabled);
|
||||
toggle.setAttribute('aria-checked', isEnabled ? 'true' : 'false');
|
||||
toggle.onclick = () => {
|
||||
toggle.classList.toggle('active');
|
||||
toggle.setAttribute('aria-checked', toggle.classList.contains('active') ? 'true' : 'false');
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getModalPreferences() {
|
||||
const preferences = {};
|
||||
cookieCategories.forEach(key => {
|
||||
const toggle = document.getElementById(`cookie-toggle-${key}`);
|
||||
preferences[key] = key === 'necessary' ? true : (toggle ? toggle.classList.contains('active') : false);
|
||||
});
|
||||
return preferences;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// EVENT LISTENERS
|
||||
// ==========================================
|
||||
|
||||
if (acceptAllBtn) {
|
||||
acceptAllBtn.addEventListener('click', () => {
|
||||
const preferences = {};
|
||||
cookieCategories.forEach(key => preferences[key] = true);
|
||||
saveConsent(preferences);
|
||||
hideBanner();
|
||||
});
|
||||
}
|
||||
|
||||
if (rejectAllBtn) {
|
||||
rejectAllBtn.addEventListener('click', () => {
|
||||
saveConsent({ necessary: true, analytics: false, marketing: false });
|
||||
hideBanner();
|
||||
});
|
||||
}
|
||||
|
||||
if (settingsBtn) {
|
||||
settingsBtn.addEventListener('click', showSettingsModal);
|
||||
}
|
||||
|
||||
if (modalCloseBtn) {
|
||||
modalCloseBtn.addEventListener('click', hideSettingsModal);
|
||||
}
|
||||
|
||||
if (savePreferencesBtn) {
|
||||
savePreferencesBtn.addEventListener('click', () => {
|
||||
saveConsent(getModalPreferences());
|
||||
hideSettingsModal();
|
||||
hideBanner();
|
||||
});
|
||||
}
|
||||
|
||||
if (cookieModal) {
|
||||
cookieModal.addEventListener('click', (e) => {
|
||||
if (e.target === cookieModal) hideSettingsModal();
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && cookieModal && cookieModal.classList.contains('show')) {
|
||||
hideSettingsModal();
|
||||
}
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// PUBLIC API
|
||||
// ==========================================
|
||||
|
||||
window.CookieConsent = {
|
||||
getConsent: () => consentState,
|
||||
hasConsent: (category) => consentState.preferences[category] === true,
|
||||
updateConsent: saveConsent,
|
||||
showSettings: showSettingsModal
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// INITIALIZE
|
||||
// ==========================================
|
||||
|
||||
initCookieConsent();
|
||||
});
|
||||
210
Profice WebSite/scripts/cursor.js
Normal file
210
Profice WebSite/scripts/cursor.js
Normal file
@@ -0,0 +1,210 @@
|
||||
// cursor.js
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
if (window.matchMedia("(pointer: coarse)").matches) return;
|
||||
|
||||
// --- SETTINGS ---
|
||||
const CONFIG = {
|
||||
tentacleCount: 8,
|
||||
triggerDist: 10,
|
||||
maxLength: 300,
|
||||
connectionDist: 150,
|
||||
thickness: 1,
|
||||
color: "rgba(20, 20, 20, 1)",
|
||||
prediction: 3.5
|
||||
};
|
||||
|
||||
|
||||
const toggleBtn = document.getElementById('cursorToggle');
|
||||
const body = document.body;
|
||||
|
||||
// Default to system cursor (disabled custom cursor)
|
||||
let isCursorDisabled = localStorage.getItem('venomCursorDisabled') !== 'false';
|
||||
|
||||
function updateCursorState() {
|
||||
if (isCursorDisabled) {
|
||||
// System cursor (default) - show spidy icon
|
||||
body.classList.add('system-cursor');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.classList.remove('active');
|
||||
const icon = toggleBtn.querySelector('.cursor-icon');
|
||||
if (icon) {
|
||||
// Check if we're on a page in the sites/ folder
|
||||
const currentPath = window.location.pathname;
|
||||
const isInSitesFolder = currentPath.includes('/sites/');
|
||||
const imagePath = isInSitesFolder ? '../images/additional/spidy.png' : 'images/additional/spidy.png';
|
||||
icon.src = imagePath;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Custom cursor (secondary) - show cursor icon
|
||||
body.classList.remove('system-cursor');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.classList.add('active');
|
||||
const icon = toggleBtn.querySelector('.cursor-icon');
|
||||
if (icon) {
|
||||
// Check if we're on a page in the sites/ folder
|
||||
const currentPath = window.location.pathname;
|
||||
const isInSitesFolder = currentPath.includes('/sites/');
|
||||
const imagePath = isInSitesFolder ? '../images/additional/cursor.png' : 'images/additional/cursor.png';
|
||||
icon.src = imagePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateCursorState();
|
||||
|
||||
if (toggleBtn) {
|
||||
toggleBtn.addEventListener('click', () => {
|
||||
isCursorDisabled = !isCursorDisabled;
|
||||
localStorage.setItem('venomCursorDisabled', isCursorDisabled);
|
||||
updateCursorState();
|
||||
});
|
||||
}
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.id = "venom-cursor";
|
||||
document.body.appendChild(canvas);
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
const width = window.innerWidth;
|
||||
const height = window.innerHeight;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
const tentacles = [];
|
||||
const mouse = { x: 0, y: 0 };
|
||||
const oldMouse = { x: 0, y: 0 };
|
||||
|
||||
document.addEventListener("mousemove", (e) => {
|
||||
mouse.x = e.clientX;
|
||||
mouse.y = e.clientY;
|
||||
});
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
});
|
||||
|
||||
class Tentacle {
|
||||
constructor(mx, my, targetX, targetY) {
|
||||
this.dead = false;
|
||||
this.anchor = { x: targetX, y: targetY };
|
||||
this.currentDist = 0; // For calculating connection opacity
|
||||
}
|
||||
|
||||
update(currentMouse) {
|
||||
const dx = currentMouse.x - this.anchor.x;
|
||||
const dy = currentMouse.y - this.anchor.y;
|
||||
this.currentDist = Math.sqrt(dx*dx + dy*dy);
|
||||
|
||||
if (this.currentDist > CONFIG.maxLength) {
|
||||
this.dead = true;
|
||||
}
|
||||
}
|
||||
|
||||
draw(ctx, currentMouse) {
|
||||
if (this.dead) return;
|
||||
|
||||
// Tension (0..1)
|
||||
const tension = Math.min(this.currentDist / CONFIG.maxLength, 1);
|
||||
const dynamicThickness = CONFIG.thickness * (1 - tension * 0.9);
|
||||
|
||||
// Draw main line (Cursor -> Anchor)
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(currentMouse.x, currentMouse.y);
|
||||
ctx.lineTo(this.anchor.x, this.anchor.y);
|
||||
|
||||
ctx.lineWidth = Math.max(0.2, dynamicThickness);
|
||||
ctx.strokeStyle = CONFIG.color;
|
||||
ctx.lineCap = "butt";
|
||||
ctx.stroke();
|
||||
|
||||
// Draw anchor point
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.anchor.x, this.anchor.y, 1.5 * (1 - tension), 0, Math.PI * 2);
|
||||
ctx.fillStyle = CONFIG.color;
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// 1. Create new tentacles on movement
|
||||
const distMoved = Math.hypot(mouse.x - oldMouse.x, mouse.y - oldMouse.y);
|
||||
|
||||
if (distMoved > CONFIG.triggerDist) {
|
||||
const vx = mouse.x - oldMouse.x;
|
||||
const vy = mouse.y - oldMouse.y;
|
||||
|
||||
// "Spread" of shots increased slightly for better geometry
|
||||
const targetX = mouse.x + vx * CONFIG.prediction + (Math.random() - 0.5) * 60;
|
||||
const targetY = mouse.y + vy * CONFIG.prediction + (Math.random() - 0.5) * 60;
|
||||
|
||||
tentacles.push(new Tentacle(mouse.x, mouse.y, targetX, targetY));
|
||||
oldMouse.x = mouse.x;
|
||||
oldMouse.y = mouse.y;
|
||||
}
|
||||
|
||||
// Remove old ones (FIFO)
|
||||
if (tentacles.length > CONFIG.tentacleCount) {
|
||||
tentacles.shift();
|
||||
}
|
||||
|
||||
// 2. Draw main tentacles
|
||||
for (let i = tentacles.length - 1; i >= 0; i--) {
|
||||
const t = tentacles[i];
|
||||
t.update(mouse);
|
||||
if (t.dead) {
|
||||
tentacles.splice(i, 1);
|
||||
} else {
|
||||
t.draw(ctx, mouse);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. DRAW CONNECTIONS BETWEEN ANCHORS (New logic)
|
||||
// Iterate through all pairs of active tentacles
|
||||
ctx.beginPath(); // Begin common path for optimization
|
||||
ctx.lineWidth = 0.5; // Connections are always thin
|
||||
|
||||
for (let i = 0; i < tentacles.length; i++) {
|
||||
for (let j = i + 1; j < tentacles.length; j++) {
|
||||
const t1 = tentacles[i];
|
||||
const t2 = tentacles[j];
|
||||
|
||||
// Calculate distance between ends of two tentacles
|
||||
const dx = t1.anchor.x - t2.anchor.x;
|
||||
const dy = t1.anchor.y - t2.anchor.y;
|
||||
const dist = Math.sqrt(dx*dx + dy*dy);
|
||||
|
||||
// If they are close to each other — connect
|
||||
if (dist < CONFIG.connectionDist) {
|
||||
// Opacity depends on how far apart they are
|
||||
// And how much the tentacles themselves are stretched
|
||||
const alpha = (1 - dist / CONFIG.connectionDist) * 0.6;
|
||||
|
||||
ctx.beginPath(); // New path for each to control opacity
|
||||
ctx.strokeStyle = `rgba(20, 20, 20, ${alpha})`;
|
||||
ctx.moveTo(t1.anchor.x, t1.anchor.y);
|
||||
ctx.lineTo(t2.anchor.x, t2.anchor.y);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Cursor (Rhombus)
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = CONFIG.color;
|
||||
ctx.moveTo(mouse.x, mouse.y - 5);
|
||||
ctx.lineTo(mouse.x + 5, mouse.y);
|
||||
ctx.lineTo(mouse.x, mouse.y + 5);
|
||||
ctx.lineTo(mouse.x - 5, mouse.y);
|
||||
ctx.fill();
|
||||
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
|
||||
render();
|
||||
});
|
||||
124
Profice WebSite/scripts/details.js
Normal file
124
Profice WebSite/scripts/details.js
Normal file
@@ -0,0 +1,124 @@
|
||||
// details.js
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const serviceDetails = {
|
||||
'seo-optimierung': {
|
||||
title: 'SEO Optimierung',
|
||||
icon: '🔍',
|
||||
description: 'Verbessern Sie Ihre Sichtbarkeit in Suchmaschinen und erreichen Sie mehr Kunden online.',
|
||||
fullDescription: 'Unsere SEO-Optimierungsdienste helfen Ihnen, in Suchmaschinen besser gefunden zu werden. Wir analysieren Ihre Website, identifizieren Optimierungspotenziale und implementieren bewährte Strategien, um Ihr Ranking zu verbessern und mehr qualifizierte Besucher anzuziehen.',
|
||||
features: [
|
||||
'Keyword-Analyse und -Recherche',
|
||||
'On-Page Optimierung',
|
||||
'Technische SEO-Audits',
|
||||
'Content-Strategie',
|
||||
'Link-Building',
|
||||
'Performance-Tracking'
|
||||
],
|
||||
benefits: [
|
||||
'Höhere Sichtbarkeit in Suchmaschinen',
|
||||
'Mehr qualifizierte Besucher',
|
||||
'Verbesserte Conversion-Raten',
|
||||
'Langfristiger ROI'
|
||||
]
|
||||
},
|
||||
'cloud-migration': {
|
||||
title: 'Cloud Migration',
|
||||
icon: '☁️',
|
||||
description: 'Modernisieren Sie Ihre IT-Infrastruktur mit sicheren und skalierbaren Cloud-Lösungen.',
|
||||
fullDescription: 'Wir begleiten Sie bei der Migration Ihrer IT-Systeme in die Cloud. Von der Analyse Ihrer bestehenden Infrastruktur bis zur vollständigen Implementierung sorgen wir für einen reibungslosen Übergang mit minimalem Geschäftsausfall.',
|
||||
features: [
|
||||
'Infrastruktur-Analyse',
|
||||
'Migrationsplanung',
|
||||
'Datenübertragung',
|
||||
'Sicherheit & Compliance',
|
||||
'Performance-Optimierung',
|
||||
'Schulung & Support'
|
||||
],
|
||||
benefits: [
|
||||
'Kosteneinsparungen',
|
||||
'Bessere Skalierbarkeit',
|
||||
'Erhöhte Sicherheit',
|
||||
'Flexiblere Arbeitsweise'
|
||||
]
|
||||
},
|
||||
'datenanalyse': {
|
||||
title: 'Datenanalyse',
|
||||
icon: '📊',
|
||||
description: 'Gewinnen Sie wertvolle Einblicke aus Ihren Daten mit unseren Analyse-Lösungen.',
|
||||
fullDescription: 'Wir helfen Ihnen, Ihre Daten in wertvolle Erkenntnisse umzuwandeln. Mit modernen Analyse-Tools und -Methoden identifizieren wir Trends, Muster und Chancen, die Ihre Geschäftsentscheidungen verbessern.',
|
||||
features: [
|
||||
'Datenerfassung & -bereinigung',
|
||||
'Statistische Analyse',
|
||||
'Visualisierung & Dashboards',
|
||||
'Predictive Analytics',
|
||||
'Berichterstellung',
|
||||
'Consulting'
|
||||
],
|
||||
benefits: [
|
||||
'Bessere Entscheidungen',
|
||||
'Prozessoptimierung',
|
||||
'Kostensenkungen',
|
||||
'Wettbewerbsvorteile'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const serviceId = urlParams.get('service');
|
||||
const detailsContainer = document.getElementById('service-details');
|
||||
|
||||
if (serviceId && serviceDetails[serviceId]) {
|
||||
const service = serviceDetails[serviceId];
|
||||
|
||||
detailsContainer.innerHTML = `
|
||||
<div class="service-header">
|
||||
<div class="service-icon-large">${service.icon}</div>
|
||||
<h1 class="service-title-large">${service.title}</h1>
|
||||
<p class="service-description-large">${service.description}</p>
|
||||
</div>
|
||||
|
||||
<div class="service-content">
|
||||
<section class="service-section">
|
||||
<h2 class="section-title">Beschreibung</h2>
|
||||
<p class="section-text">${service.fullDescription}</p>
|
||||
</section>
|
||||
|
||||
<section class="service-section">
|
||||
<h2 class="section-title">Unsere Leistungen</h2>
|
||||
<ul class="feature-list">
|
||||
${service.features.map(feature => `<li>${feature}</li>`).join('')}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="service-section">
|
||||
<h2 class="section-title">Ihre Vorteile</h2>
|
||||
<ul class="benefit-list">
|
||||
${service.benefits.map(benefit => `<li>${benefit}</li>`).join('')}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="service-section">
|
||||
<h2 class="section-title">Interesse?</h2>
|
||||
<p class="section-text">Kontaktieren Sie uns für eine persönliche Beratung und ein maßgeschneidertes Angebot.</p>
|
||||
<div class="cta-buttons">
|
||||
<a href="offers.html" class="cta-btn primary">Kontakt aufnehmen</a>
|
||||
<a href="leads.html" class="cta-btn secondary">Zurück zum Dashboard</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Update page title
|
||||
document.title = `Profice - ${service.title}`;
|
||||
} else {
|
||||
// Fallback if no service specified
|
||||
detailsContainer.innerHTML = `
|
||||
<div class="error-message">
|
||||
<h2>Service nicht gefunden</h2>
|
||||
<p>Der angeforderte Service konnte nicht gefunden werden.</p>
|
||||
<a href="leads.html" class="cta-btn secondary">Zurück zum Dashboard</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
203
Profice WebSite/scripts/lead-details.js
Normal file
203
Profice WebSite/scripts/lead-details.js
Normal file
@@ -0,0 +1,203 @@
|
||||
// lead-details.js
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const leadId = urlParams.get('id');
|
||||
const detailsContainer = document.getElementById('leadDetailsContent');
|
||||
|
||||
function getLeads() {
|
||||
const storedLeads = localStorage.getItem('myLeads');
|
||||
if (storedLeads) {
|
||||
return JSON.parse(storedLeads);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusClass(status) {
|
||||
const statusClasses = {
|
||||
'neu': 'status-new',
|
||||
'in-bearbeitung': 'status-in-progress',
|
||||
'abgeschlossen': 'status-completed',
|
||||
'storniert': 'status-cancelled'
|
||||
};
|
||||
return statusClasses[status] || 'status-new';
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('de-DE', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
if (leadId && detailsContainer) {
|
||||
const leadsData = getLeads();
|
||||
const lead = leadsData.find(l => l.id == leadId);
|
||||
|
||||
if (lead) {
|
||||
detailsContainer.innerHTML = `
|
||||
<div class="lead-details-card">
|
||||
<!-- Status Section -->
|
||||
<div class="detail-section">
|
||||
<h2 class="section-title">Status</h2>
|
||||
<div class="status-container">
|
||||
<span class="status-badge ${getStatusClass(lead.status)}">${lead.statusText}</span>
|
||||
<p class="status-info">Letzte Aktualisierung: ${formatDate(lead.datum)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact Information -->
|
||||
<div class="detail-section">
|
||||
<h2 class="section-title">Kontaktinformationen</h2>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<div class="info-label">Name</div>
|
||||
<div class="info-value">${lead.name || 'Nicht angegeben'}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Organisation</div>
|
||||
<div class="info-value">${lead.organisation || 'Nicht angegeben'}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Kontakt</div>
|
||||
<div class="info-value">${lead.contact || 'Nicht angegeben'}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Budget</div>
|
||||
<div class="info-value">${lead.budget || 'Nicht angegeben'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Service Information -->
|
||||
<div class="detail-section">
|
||||
<h2 class="section-title">Dienstleistung</h2>
|
||||
<div class="service-info">
|
||||
<div class="service-icon">${getServiceIcon(lead.dienstleistung)}</div>
|
||||
<div class="service-details">
|
||||
<h3 class="service-title">${lead.dienstleistung}</h3>
|
||||
<p class="service-description">${getServiceDescription(lead.dienstleistung)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Project Description -->
|
||||
${lead.description ? `
|
||||
<div class="detail-section">
|
||||
<h2 class="section-title">Projektbeschreibung</h2>
|
||||
<div class="description-box">
|
||||
<p class="description-text">${lead.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Timeline -->
|
||||
<div class="detail-section">
|
||||
<h2 class="section-title">Zeitstrahl</h2>
|
||||
<div class="timeline">
|
||||
<div class="timeline-item active">
|
||||
<div class="timeline-dot"></div>
|
||||
<div class="timeline-content">
|
||||
<h4>Anfrage erhalten</h4>
|
||||
<p>${formatDate(lead.datum)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item ${lead.status !== 'neu' ? 'active' : ''}">
|
||||
<div class="timeline-dot"></div>
|
||||
<div class="timeline-content">
|
||||
<h4>In Bearbeitung</h4>
|
||||
<p>${lead.status !== 'neu' ? 'Anfrage wird bearbeitet' : 'Ausstehend'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item ${lead.status === 'abgeschlossen' ? 'active' : ''}">
|
||||
<div class="timeline-dot"></div>
|
||||
<div class="timeline-content">
|
||||
<h4>Abgeschlossen</h4>
|
||||
<p>${lead.status === 'abgeschlossen' ? 'Projekt erfolgreich abgeschlossen' : 'Ausstehend'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="detail-section">
|
||||
<h2 class="section-title">Aktionen</h2>
|
||||
<div class="action-buttons">
|
||||
<button class="action-btn primary" onclick="window.print()">
|
||||
🖨️ Details drucken
|
||||
</button>
|
||||
<button class="action-btn secondary" onclick="shareLead()">
|
||||
📤 Teilen
|
||||
</button>
|
||||
<button class="action-btn secondary" onclick="exportLead()">
|
||||
📄 Exportieren
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Update page title
|
||||
document.title = `Profice - Anfrage von ${lead.name || 'Unbekannt'}`;
|
||||
} else {
|
||||
detailsContainer.innerHTML = `
|
||||
<div class="error-message">
|
||||
<h2>Anfrage nicht gefunden</h2>
|
||||
<p>Die angeforderte Anfrage konnte nicht gefunden werden.</p>
|
||||
<a href="leads.html" class="cta-btn secondary">Zurück zum Dashboard</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} else {
|
||||
detailsContainer.innerHTML = `
|
||||
<div class="error-message">
|
||||
<h2>Keine Anfrage-ID angegeben</h2>
|
||||
<p>Bitte navigieren Sie über das Leads Dashboard zu den Details.</p>
|
||||
<a href="leads.html" class="cta-btn secondary">Zum Dashboard</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
function getServiceIcon(service) {
|
||||
const icons = {
|
||||
'Website': '🌐',
|
||||
'KI Integration': '🤖',
|
||||
'Automatisation': '⚙️',
|
||||
'Unabhängige Wahl': '🎯'
|
||||
};
|
||||
return icons[service] || '📋';
|
||||
}
|
||||
|
||||
function getServiceDescription(service) {
|
||||
const descriptions = {
|
||||
'Website': 'Moderne, responsive Webseiten, die konvertieren und Ihre Marke perfekt repräsentieren.',
|
||||
'KI Integration': 'Nutzen Sie die Kraft künstlicher Intelligenz, um Ihre Daten besser zu verstehen und Prozesse zu optimieren.',
|
||||
'Automatisation': 'Sparen Sie Zeit und Ressourcen durch intelligente Workflow-Automatisierungen.',
|
||||
'Unabhängige Wahl': 'Maßgeschneiderte Lösungen für Ihre spezifischen Anforderungen.'
|
||||
};
|
||||
return descriptions[service] || 'Individuelle Dienstleistung.';
|
||||
}
|
||||
|
||||
function shareLead() {
|
||||
if (navigator.share) {
|
||||
navigator.share({
|
||||
title: document.title,
|
||||
text: 'Details meiner Projektanfrage bei Profice',
|
||||
url: window.location.href
|
||||
});
|
||||
} else {
|
||||
// Fallback for browsers that don't support Web Share API
|
||||
navigator.clipboard.writeText(window.location.href);
|
||||
alert('Link wurde in die Zwischenablage kopiert!');
|
||||
}
|
||||
}
|
||||
|
||||
function exportLead() {
|
||||
window.print();
|
||||
}
|
||||
75
Profice WebSite/scripts/leads.js
Normal file
75
Profice WebSite/scripts/leads.js
Normal file
@@ -0,0 +1,75 @@
|
||||
// leads.js
|
||||
|
||||
// ==========================================
|
||||
// 1. MENU TOGGLE
|
||||
// ==========================================
|
||||
const menuToggle = document.getElementById('menuToggle');
|
||||
const slideMenu = document.getElementById('slideMenu');
|
||||
const overlay = document.getElementById('overlay');
|
||||
|
||||
if (menuToggle && slideMenu && overlay) {
|
||||
function toggleMenu() {
|
||||
menuToggle.classList.toggle('active');
|
||||
slideMenu.classList.toggle('active');
|
||||
overlay.classList.toggle('active');
|
||||
}
|
||||
|
||||
menuToggle.addEventListener('click', toggleMenu);
|
||||
overlay.addEventListener('click', toggleMenu);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 2. LEADS TABLE LOGIC
|
||||
// ==========================================
|
||||
|
||||
function getLeads() {
|
||||
const storedLeads = localStorage.getItem('myLeads');
|
||||
if (storedLeads) {
|
||||
return JSON.parse(storedLeads);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function populateLeadsTable() {
|
||||
const tableBody = document.getElementById('leadsTableBody');
|
||||
const leadsData = getLeads();
|
||||
|
||||
if (!tableBody) return;
|
||||
|
||||
if (leadsData.length === 0) {
|
||||
tableBody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="4" class="empty-state">
|
||||
<p>Keine Anfragen vorhanden</p>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
tableBody.innerHTML = leadsData.map(lead => `
|
||||
<tr>
|
||||
<td>${lead.datum}</td>
|
||||
<td>${lead.dienstleistung}</td>
|
||||
<td>
|
||||
<span class="status-badge ${lead.status}">${lead.statusText}</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" class="action-btn" data-id="${lead.id}">Details ansehen</a>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
document.querySelectorAll('.action-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const leadId = this.getAttribute('data-id');
|
||||
|
||||
// Redirect to lead details page
|
||||
window.location.href = `lead-details.html?id=${leadId}`;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', populateLeadsTable);
|
||||
217
Profice WebSite/scripts/login.js
Normal file
217
Profice WebSite/scripts/login.js
Normal file
@@ -0,0 +1,217 @@
|
||||
// login.js
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
const loginBtn = document.getElementById('loginSubmit');
|
||||
const successMessage = document.getElementById('successMessage');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
const errorText = document.getElementById('errorText');
|
||||
const registerBtn = document.getElementById('registerBtn');
|
||||
|
||||
// Register button functionality
|
||||
if (registerBtn) {
|
||||
registerBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
window.location.href = 'register.html';
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user is already logged in
|
||||
checkExistingSession();
|
||||
|
||||
// Login form submission
|
||||
if (loginForm) {
|
||||
loginForm.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const email = document.getElementById('email').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const remember = document.getElementById('remember').checked;
|
||||
|
||||
// Simple validation
|
||||
if (!email || !password) {
|
||||
showError('Bitte füllen Sie alle Felder aus.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Email validation
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
showError('Bitte geben Sie eine gültige E-Mail-Adresse ein.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
setLoadingState(true);
|
||||
hideMessages();
|
||||
|
||||
// Send login data to PHP API
|
||||
const loginData = {
|
||||
type: 'login',
|
||||
email: email,
|
||||
password: password,
|
||||
remember: remember
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('../scripts/add/send.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(loginData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Store session (for demo purposes - in real app, this would come from server)
|
||||
const sessionData = {
|
||||
user: { email: email },
|
||||
loginTime: new Date().toISOString(),
|
||||
remember: remember
|
||||
};
|
||||
|
||||
if (remember) {
|
||||
localStorage.setItem('userSession', JSON.stringify(sessionData));
|
||||
} else {
|
||||
sessionStorage.setItem('userSession', JSON.stringify(sessionData));
|
||||
}
|
||||
|
||||
// Show success message
|
||||
showSuccess('Anmeldung erfolgreich! Sie werden weitergeleitet...');
|
||||
|
||||
// Redirect after delay
|
||||
setTimeout(() => {
|
||||
window.location.href = '../index.html';
|
||||
}, 2000);
|
||||
} else {
|
||||
showError('Anmeldung fehlgeschlagen: ' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
showError('Netzwerkfehler bei der Anmeldung. Bitte versuchen Sie es später erneut.');
|
||||
}
|
||||
|
||||
setLoadingState(false);
|
||||
});
|
||||
}
|
||||
|
||||
// Loading state management
|
||||
function setLoadingState(loading) {
|
||||
if (loginBtn) {
|
||||
const btnText = loginBtn.querySelector('.btn-text');
|
||||
const btnLoading = loginBtn.querySelector('.btn-loading');
|
||||
|
||||
if (loading) {
|
||||
loginBtn.disabled = true;
|
||||
if (btnText) btnText.style.display = 'none';
|
||||
if (btnLoading) btnLoading.style.display = 'inline-block';
|
||||
} else {
|
||||
loginBtn.disabled = false;
|
||||
if (btnText) btnText.style.display = 'inline-block';
|
||||
if (btnLoading) btnLoading.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Message display functions
|
||||
function showSuccess(message) {
|
||||
if (successMessage) {
|
||||
const messageElement = successMessage.querySelector('p');
|
||||
if (messageElement) {
|
||||
messageElement.textContent = message;
|
||||
}
|
||||
successMessage.classList.add('show');
|
||||
}
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
if (errorMessage && errorText) {
|
||||
errorText.textContent = message;
|
||||
errorMessage.classList.add('show');
|
||||
|
||||
// Hide after 5 seconds
|
||||
setTimeout(() => {
|
||||
errorMessage.classList.remove('show');
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function hideMessages() {
|
||||
if (successMessage) successMessage.classList.remove('show');
|
||||
if (errorMessage) errorMessage.classList.remove('show');
|
||||
}
|
||||
});
|
||||
|
||||
// Check existing session - DISABLED auto-redirect
|
||||
function checkExistingSession() {
|
||||
const sessionData = localStorage.getItem('userSession') || sessionStorage.getItem('userSession');
|
||||
|
||||
if (sessionData) {
|
||||
try {
|
||||
const session = JSON.parse(sessionData);
|
||||
const loginTime = new Date(session.loginTime);
|
||||
const now = new Date();
|
||||
const sessionAge = (now - loginTime) / (1000 * 60 * 60); // hours
|
||||
|
||||
// Auto-logout after 24 hours
|
||||
if (sessionAge < 24) {
|
||||
// User is still logged in - just log it, don't redirect
|
||||
console.log('User already logged in');
|
||||
// DISABLED: window.location.href = '../index.html';
|
||||
} else {
|
||||
// Session expired, remove it
|
||||
console.log('Session expired, removing...');
|
||||
localStorage.removeItem('userSession');
|
||||
sessionStorage.removeItem('userSession');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Session parsing error:', error);
|
||||
localStorage.removeItem('userSession');
|
||||
sessionStorage.removeItem('userSession');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Logout function (can be called from other pages)
|
||||
function logout() {
|
||||
localStorage.removeItem('userSession');
|
||||
sessionStorage.removeItem('userSession');
|
||||
window.location.href = 'sites/login.html';
|
||||
}
|
||||
|
||||
// Get current user (can be called from other pages)
|
||||
function getCurrentUser() {
|
||||
const sessionData = localStorage.getItem('userSession') || sessionStorage.getItem('userSession');
|
||||
|
||||
if (sessionData) {
|
||||
try {
|
||||
const session = JSON.parse(sessionData);
|
||||
const loginTime = new Date(session.loginTime);
|
||||
const now = new Date();
|
||||
const sessionAge = (now - loginTime) / (1000 * 60 * 60); // hours
|
||||
|
||||
if (sessionAge < 24) {
|
||||
return session.user;
|
||||
} else {
|
||||
// Session expired
|
||||
localStorage.removeItem('userSession');
|
||||
sessionStorage.removeItem('userSession');
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Session parsing error:', error);
|
||||
localStorage.removeItem('userSession');
|
||||
sessionStorage.removeItem('userSession');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if user is logged in (can be called from other pages)
|
||||
function isLoggedIn() {
|
||||
return getCurrentUser() !== null;
|
||||
}
|
||||
272
Profice WebSite/scripts/register.js
Normal file
272
Profice WebSite/scripts/register.js
Normal file
@@ -0,0 +1,272 @@
|
||||
// register.js
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const registerForm = document.getElementById('registerForm');
|
||||
const registerBtn = document.getElementById('registerSubmit');
|
||||
const successMessage = document.getElementById('successMessage');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
const errorText = document.getElementById('errorText');
|
||||
const passwordInput = document.getElementById('password');
|
||||
const confirmPasswordInput = document.getElementById('confirmPassword');
|
||||
const passwordStrength = document.getElementById('passwordStrength');
|
||||
|
||||
// Check if user is already logged in
|
||||
checkExistingSession();
|
||||
|
||||
// Password strength checker
|
||||
if (passwordInput && passwordStrength) {
|
||||
passwordInput.addEventListener('input', function() {
|
||||
const password = this.value;
|
||||
const strength = checkPasswordStrength(password);
|
||||
updatePasswordStrength(strength);
|
||||
});
|
||||
}
|
||||
|
||||
// Password confirmation checker
|
||||
if (confirmPasswordInput && passwordInput) {
|
||||
confirmPasswordInput.addEventListener('input', function() {
|
||||
const password = passwordInput.value;
|
||||
const confirmPassword = this.value;
|
||||
|
||||
if (confirmPassword && password !== confirmPassword) {
|
||||
this.setCustomValidity('Passwörter stimmen nicht überein');
|
||||
} else {
|
||||
this.setCustomValidity('');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Registration form submission
|
||||
if (registerForm) {
|
||||
registerForm.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Get form data
|
||||
const formData = {
|
||||
type: 'register',
|
||||
name: `${document.getElementById('firstName').value} ${document.getElementById('lastName').value}`,
|
||||
email: document.getElementById('email').value,
|
||||
phone: document.getElementById('phone').value,
|
||||
company: document.getElementById('company').value,
|
||||
password: passwordInput.value,
|
||||
confirmPassword: confirmPasswordInput.value,
|
||||
terms: document.getElementById('terms').checked,
|
||||
newsletter: document.getElementById('newsletter').checked,
|
||||
registrationTime: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Validation
|
||||
const validation = validateRegistrationForm(formData);
|
||||
if (!validation.valid) {
|
||||
showError(validation.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
setLoadingState(true);
|
||||
hideMessages();
|
||||
|
||||
// Send registration data to PHP API
|
||||
try {
|
||||
const response = await fetch('../scripts/add/send.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Store user data (for demo purposes)
|
||||
const userData = {
|
||||
firstName: document.getElementById('firstName').value,
|
||||
lastName: document.getElementById('lastName').value,
|
||||
email: formData.email,
|
||||
phone: formData.phone,
|
||||
company: formData.company,
|
||||
registrationTime: formData.registrationTime,
|
||||
newsletter: formData.newsletter
|
||||
};
|
||||
|
||||
// Store in localStorage (for demo purposes)
|
||||
localStorage.setItem('userData', JSON.stringify(userData));
|
||||
localStorage.setItem('userRegistered', 'true');
|
||||
|
||||
// Show success message
|
||||
showSuccess('Registrierung erfolgreich! Sie werden weitergeleitet...');
|
||||
|
||||
// Redirect after delay
|
||||
setTimeout(() => {
|
||||
window.location.href = '../index.html';
|
||||
}, 2000);
|
||||
} else {
|
||||
showError('Registrierung fehlgeschlagen: ' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
showError('Netzwerkfehler bei der Registrierung. Bitte versuchen Sie es später erneut.');
|
||||
}
|
||||
|
||||
setLoadingState(false);
|
||||
});
|
||||
}
|
||||
|
||||
// Password strength checker function
|
||||
function checkPasswordStrength(password) {
|
||||
let strength = 0;
|
||||
|
||||
// Length check
|
||||
if (password.length >= 8) strength++;
|
||||
if (password.length >= 12) strength++;
|
||||
|
||||
// Character variety checks
|
||||
if (/[a-z]/.test(password)) strength++; // lowercase
|
||||
if (/[A-Z]/.test(password)) strength++; // uppercase
|
||||
if (/[0-9]/.test(password)) strength++; // numbers
|
||||
if (/[^a-zA-Z0-9]/.test(password)) strength++; // special characters
|
||||
|
||||
return strength;
|
||||
}
|
||||
|
||||
// Update password strength indicator
|
||||
function updatePasswordStrength(strength) {
|
||||
const strengthBar = passwordStrength.querySelector('.strength-bar');
|
||||
const strengthText = passwordStrength.querySelector('.strength-text');
|
||||
|
||||
// Remove all strength classes
|
||||
passwordStrength.classList.remove('strength-weak', 'strength-medium', 'strength-strong');
|
||||
|
||||
if (strength <= 2) {
|
||||
passwordStrength.classList.add('strength-weak');
|
||||
strengthText.textContent = 'Schwach';
|
||||
} else if (strength <= 4) {
|
||||
passwordStrength.classList.add('strength-medium');
|
||||
strengthText.textContent = 'Mittel';
|
||||
} else {
|
||||
passwordStrength.classList.add('strength-strong');
|
||||
strengthText.textContent = 'Stark';
|
||||
}
|
||||
}
|
||||
|
||||
// Form validation
|
||||
function validateRegistrationForm(data) {
|
||||
// Required fields check
|
||||
if (!data.firstName || !data.lastName || !data.email || !data.password) {
|
||||
return { valid: false, message: 'Bitte füllen Sie alle Pflichtfelder aus.' };
|
||||
}
|
||||
|
||||
// Name validation
|
||||
if (data.firstName.length < 2 || data.lastName.length < 2) {
|
||||
return { valid: false, message: 'Vorname und Nachname müssen mindestens 2 Zeichen lang sein.' };
|
||||
}
|
||||
|
||||
// Email validation
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(data.email)) {
|
||||
return { valid: false, message: 'Bitte geben Sie eine gültige E-Mail-Adresse ein.' };
|
||||
}
|
||||
|
||||
// Password validation
|
||||
if (data.password.length < 8) {
|
||||
return { valid: false, message: 'Das Passwort muss mindestens 8 Zeichen lang sein.' };
|
||||
}
|
||||
|
||||
// Password confirmation
|
||||
if (data.password !== data.confirmPassword) {
|
||||
return { valid: false, message: 'Die Passwörter stimmen nicht überein.' };
|
||||
}
|
||||
|
||||
// Terms acceptance
|
||||
if (!data.terms) {
|
||||
return { valid: false, message: 'Sie müssen die Nutzungsbedingungen akzeptieren.' };
|
||||
}
|
||||
|
||||
// Phone validation (if provided)
|
||||
if (data.phone) {
|
||||
const phoneRegex = /^[\d\s\-\+\(\)]+$/;
|
||||
if (!phoneRegex.test(data.phone)) {
|
||||
return { valid: false, message: 'Bitte geben Sie eine gültige Telefonnummer ein.' };
|
||||
}
|
||||
}
|
||||
|
||||
return { valid: true, message: 'Validierung erfolgreich' };
|
||||
}
|
||||
|
||||
// Loading state management
|
||||
function setLoadingState(loading) {
|
||||
if (registerBtn) {
|
||||
const btnText = registerBtn.querySelector('.btn-text');
|
||||
const btnLoading = registerBtn.querySelector('.btn-loading');
|
||||
|
||||
if (loading) {
|
||||
registerBtn.disabled = true;
|
||||
if (btnText) btnText.style.display = 'none';
|
||||
if (btnLoading) btnLoading.style.display = 'inline-block';
|
||||
} else {
|
||||
registerBtn.disabled = false;
|
||||
if (btnText) btnText.style.display = 'inline-block';
|
||||
if (btnLoading) btnLoading.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Message display functions
|
||||
function showSuccess(message) {
|
||||
if (successMessage) {
|
||||
const messageElement = successMessage.querySelector('p');
|
||||
if (messageElement) {
|
||||
messageElement.textContent = message;
|
||||
}
|
||||
successMessage.classList.add('show');
|
||||
}
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
if (errorMessage && errorText) {
|
||||
errorText.textContent = message;
|
||||
errorMessage.classList.add('show');
|
||||
|
||||
// Hide after 5 seconds
|
||||
setTimeout(() => {
|
||||
errorMessage.classList.remove('show');
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function hideMessages() {
|
||||
if (successMessage) successMessage.classList.remove('show');
|
||||
if (errorMessage) errorMessage.classList.remove('show');
|
||||
}
|
||||
});
|
||||
|
||||
// Check existing session (same as login.js) - DISABLED auto-redirect
|
||||
function checkExistingSession() {
|
||||
const sessionData = localStorage.getItem('userSession') || sessionStorage.getItem('userSession');
|
||||
|
||||
if (sessionData) {
|
||||
try {
|
||||
const session = JSON.parse(sessionData);
|
||||
const loginTime = new Date(session.loginTime);
|
||||
const now = new Date();
|
||||
const sessionAge = (now - loginTime) / (1000 * 60 * 60); // hours
|
||||
|
||||
// Auto-logout after 24 hours
|
||||
if (sessionAge < 24) {
|
||||
// User is still logged in - just log it, don't redirect
|
||||
console.log('User already logged in');
|
||||
// DISABLED: window.location.href = '../index.html';
|
||||
} else {
|
||||
// Session expired, remove it
|
||||
console.log('Session expired, removing...');
|
||||
localStorage.removeItem('userSession');
|
||||
sessionStorage.removeItem('userSession');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Session parsing error:', error);
|
||||
localStorage.removeItem('userSession');
|
||||
sessionStorage.removeItem('userSession');
|
||||
}
|
||||
}
|
||||
}
|
||||
151
Profice WebSite/scripts/script.js
Normal file
151
Profice WebSite/scripts/script.js
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Main Script - Profice Website
|
||||
* All API calls go through server-side PHP
|
||||
* Includes fallback for local file access (no server)
|
||||
*
|
||||
* @version 2.1.0
|
||||
*/
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
|
||||
const API_ENDPOINT = 'scripts/add/send.php';
|
||||
const isLocalFile = window.location.protocol === 'file:';
|
||||
|
||||
// ==========================================
|
||||
// 1. SMOOTH SCROLLING
|
||||
// ==========================================
|
||||
|
||||
function initSmoothScrolling() {
|
||||
document.querySelectorAll('a[href^="#"]').forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
const targetId = this.getAttribute('href');
|
||||
if (targetId === '#') return;
|
||||
|
||||
const targetElement = document.querySelector(targetId);
|
||||
if (targetElement) {
|
||||
e.preventDefault();
|
||||
const header = document.querySelector('.top-banner');
|
||||
const headerHeight = header ? header.offsetHeight : 90;
|
||||
|
||||
window.scrollTo({
|
||||
top: targetElement.offsetTop - headerHeight - 20,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
|
||||
history.pushState(null, null, targetId);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
initSmoothScrolling();
|
||||
|
||||
// ==========================================
|
||||
// 2. MENU TOGGLE
|
||||
// ==========================================
|
||||
|
||||
const menuToggle = document.getElementById('menuToggle');
|
||||
const slideMenu = document.getElementById('slideMenu');
|
||||
const overlay = document.getElementById('overlay');
|
||||
|
||||
if (menuToggle && slideMenu && overlay) {
|
||||
const toggleMenu = () => {
|
||||
menuToggle.classList.toggle('active');
|
||||
slideMenu.classList.toggle('active');
|
||||
overlay.classList.toggle('active');
|
||||
};
|
||||
menuToggle.addEventListener('click', toggleMenu);
|
||||
overlay.addEventListener('click', toggleMenu);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 3. LOGIN BUTTON
|
||||
// ==========================================
|
||||
|
||||
const loginBtn = document.getElementById('loginBtn');
|
||||
if (loginBtn) {
|
||||
const currentPath = window.location.pathname;
|
||||
let loginPath = 'sites/login.html';
|
||||
|
||||
if (currentPath.includes('/sites/')) {
|
||||
loginPath = currentPath.includes('login.html') ? 'register.html' : 'login.html';
|
||||
}
|
||||
|
||||
loginBtn.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
window.location.href = loginPath;
|
||||
};
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 4. FORM SUBMISSION
|
||||
// ==========================================
|
||||
|
||||
const contactForm = document.getElementById('contactForm');
|
||||
const successMessage = document.getElementById('successMessage');
|
||||
|
||||
if (contactForm) {
|
||||
contactForm.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const getValue = (id) => {
|
||||
const el = document.getElementById(id);
|
||||
return el ? el.value : '';
|
||||
};
|
||||
|
||||
const serviceSelect = document.getElementById('service');
|
||||
const selectedServiceText = serviceSelect ?
|
||||
serviceSelect.options[serviceSelect.selectedIndex].text : 'Dienstleistung';
|
||||
|
||||
const formData = {
|
||||
type: 'contact',
|
||||
name: getValue('name'),
|
||||
organisation: getValue('organisation'),
|
||||
contact: getValue('contact'),
|
||||
service: getValue('service'),
|
||||
budget: getValue('budget'),
|
||||
description: getValue('description')
|
||||
};
|
||||
|
||||
// Always store locally for dashboard
|
||||
try {
|
||||
const localLead = {
|
||||
id: Date.now(),
|
||||
datum: new Date().toLocaleDateString('de-DE'),
|
||||
dienstleistung: selectedServiceText,
|
||||
status: 'open',
|
||||
statusText: 'Offen',
|
||||
description: formData.description
|
||||
};
|
||||
const existingLeads = JSON.parse(localStorage.getItem('myLeads') || '[]');
|
||||
existingLeads.unshift(localLead);
|
||||
localStorage.setItem('myLeads', JSON.stringify(existingLeads.slice(0, 100)));
|
||||
} catch (err) {}
|
||||
|
||||
// Send to API if not local file
|
||||
if (!isLocalFile) {
|
||||
try {
|
||||
const response = await fetch(API_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
alert('Fehler beim Senden: ' + (result.message || 'Unbekannter Fehler'));
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Netzwerkfehler beim Senden des Formulars');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Show success
|
||||
contactForm.style.display = 'none';
|
||||
if (successMessage) successMessage.classList.add('show');
|
||||
contactForm.reset();
|
||||
});
|
||||
}
|
||||
});
|
||||
83
Profice WebSite/scripts/scroll-header.js
Normal file
83
Profice WebSite/scripts/scroll-header.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// scroll-header.js
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const topBanner = document.querySelector('.top-banner');
|
||||
const slideMenu = document.querySelector('.slide-menu');
|
||||
|
||||
if (!topBanner) return;
|
||||
|
||||
// Scroll threshold to trigger the shrink effect
|
||||
const scrollThreshold = 50;
|
||||
let isScrolled = false;
|
||||
let isTransitioning = false;
|
||||
let lastScrollY = 0;
|
||||
let scrollDirection = 'down';
|
||||
|
||||
function updateHeaderState(scrolled) {
|
||||
if (isTransitioning) return;
|
||||
|
||||
if (scrolled && !isScrolled) {
|
||||
isTransitioning = true;
|
||||
topBanner.classList.add('scrolled');
|
||||
isScrolled = true;
|
||||
|
||||
if (slideMenu) {
|
||||
slideMenu.style.top = '80px';
|
||||
}
|
||||
|
||||
// Reset transition flag after animation completes
|
||||
setTimeout(() => {
|
||||
isTransitioning = false;
|
||||
}, 250);
|
||||
|
||||
} else if (!scrolled && isScrolled) {
|
||||
isTransitioning = true;
|
||||
topBanner.classList.remove('scrolled');
|
||||
isScrolled = false;
|
||||
|
||||
if (slideMenu) {
|
||||
slideMenu.style.top = '110px';
|
||||
}
|
||||
|
||||
// Reset transition flag after animation completes
|
||||
setTimeout(() => {
|
||||
isTransitioning = false;
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
|
||||
function handleScroll() {
|
||||
const currentScrollY = window.pageYOffset || document.documentElement.scrollTop;
|
||||
|
||||
// Detect scroll direction
|
||||
if (currentScrollY > lastScrollY) {
|
||||
scrollDirection = 'down';
|
||||
} else if (currentScrollY < lastScrollY) {
|
||||
scrollDirection = 'up';
|
||||
}
|
||||
|
||||
// Only update when crossing threshold in the right direction
|
||||
if (scrollDirection === 'down' && currentScrollY > scrollThreshold && !isScrolled) {
|
||||
updateHeaderState(true);
|
||||
} else if (scrollDirection === 'up' && currentScrollY <= scrollThreshold && isScrolled) {
|
||||
updateHeaderState(false);
|
||||
}
|
||||
|
||||
lastScrollY = currentScrollY;
|
||||
}
|
||||
|
||||
// Use requestAnimationFrame for smooth scroll handling
|
||||
let ticking = false;
|
||||
function requestTick() {
|
||||
if (!ticking) {
|
||||
requestAnimationFrame(handleScroll);
|
||||
ticking = true;
|
||||
setTimeout(() => { ticking = false; }, 16); // ~60fps
|
||||
}
|
||||
}
|
||||
|
||||
// Optimized scroll event listener
|
||||
window.addEventListener('scroll', requestTick, { passive: true, capture: false });
|
||||
|
||||
// Initial check
|
||||
handleScroll();
|
||||
});
|
||||
Reference in New Issue
Block a user