- Created new src/ directory - Copied all project files and folders to src/ - Keeps project structure organized and consolidated
133 KiB
Workflow
Thinking
Responding Verstanden - Anforderungsanalyse Ich habe folgendes verstanden:
Plattform: getyourband.ch - Bandvermittlungsplattform
Zielgruppe: Private Grossbands mit Auftritts-Erfahrung
Technologie: PHP, Single-Page-Ansatz, modernes/funkiges Design
Kernfunktionen: Vermittlung, Bewertungen, Anfragen, Ortsangaben
Fragebogen - Offene Punkte vor Programmierung
-
Datenbankstruktur & Hosting
Welches DBMS? (MySQL, PostgreSQL, SQLite?) Hosting-Umgebung vorhanden? (PHP-Version?) Soll ich SQL-Schema mitliefern?
-
Benutzerrollen & Authentifizierung
Wer kann sich registrieren? (Nur Bands / Auch Veranstalter/Kunden?) Login-System erforderlich? Registrierungsprozess mit E-Mail-Verifizierung?
-
Band-Profile
Welche Pflichtfelder? (Name, Genre, Ort, Preis, Fotos, Videos?) Mehrere Fotos/Videos pro Band? Beschreibungstexte mit Formatierung? Verfügbarkeitskalender integrieren?
-
Bewertungssystem
Wer darf bewerten? (Nur nach Buchung / Öffentlich?) Sterne-System (1-5) oder andere Skala? Textbewertungen erlaubt? Moderation von Bewertungen?
-
Anfragen/Buchungssystem
Direktbuchung oder Anfrage-Formular? Welche Daten beim Anfragen? (Datum, Ort, Budget, Event-Typ?) E-Mail-Benachrichtigungen an Band? Anfragen-Verwaltung im Backend?
-
Suchfunktionen & Filter
Suche nach: Genre, PLZ/Ort, Preis, Verfügbarkeit? Kartenansicht (Google Maps API)? Sortierung (Bewertung, Preis, Entfernung)?
-
Design & Frontend
Welcher "funky" Stil? (Neon, Retro, Modern-Minimalistisch?) Farbschema-Vorstellungen? Mobile-First wichtig? CSS-Framework gewünscht? (Tailwind, Bootstrap, Custom?)
-
Single-Page Details
Wirklich ALLES auf einer Seite (inkl. Admin/Verwaltung)? Oder mehrere Seiten (Liste, Detail, Anfrage-Formular)? JavaScript-Framework? (Vanilla JS, Alpine.js, Vue.js?)
-
Zahlungssystem
Vermittlungsgebühr? Zahlungsabwicklung integrieren? (Stripe, PayPal?) Oder nur Kontaktvermittlung ohne Zahlung?
-
Admin/Moderation
Admin-Bereich für Plattform-Betreiber? Bands können eigene Profile bearbeiten? Freischaltung neuer Bands erforderlich?
-
Rechtliches
DSGVO-Konformität wichtig? (Cookie-Banner, Datenschutz?) AGB/Impressum-Seiten nötig?
Bitte beantworte diese Punkte, damit ich eine präzise, funktionierende Lösung ohne Annahmen programmieren kann! 🎸
- am besten wo man nichts installeiren muss, php neuste version. ubuntu server mit python isntallier neuste apache verwsion, 2. alle registrieren, login sinnvoll, email verifizierung. 3. allews was sinn macht. mehrer vids pro band, ja mit formatierung. kalender einbauen. 4.nur nach buchung, mit stenern, texte auch. max 200 zeichen, moderation. 5. was modern ist. alles was sinn mach. banchrichtigung an band, verwaltung in backend6. such nach stil, fabr gelbtöne, mobil auch wirhcit . modnerste css 8. da es sicher viele funktionen gfibt mehrere files. . javascript das passendste. 9. gebühr aktiviertbar. paypal, aktivierbar sonst vermittlungsgebühr. 10admin backend. profil bearbeitbar. ja freischalten. 11. mit banner und agp, mache mehrere files, du wirds sicher mehrer schritte brauchen pro anfrage an dich Workflow
Thinking
Searching
Thinking
Responding Perfekt! Zusammenfassung vor Umsetzung Was ich verstanden habe: Technologie-Stack
Server: Ubuntu mit Apache (neueste), PHP 8.3+, Python installiert
Datenbank: SQLite (keine Installation nötig) oder MySQL
Frontend: Modernes CSS (CSS Grid/Flexbox), Vanilla JavaScript oder Alpine.js
Design: Funky, gelbe Farbtöne, mobile-responsive
Funktionsumfang ✅ Multi-User (Bands + Veranstalter) ✅ Login + E-Mail-Verifizierung ✅ Band-Profile (mehrere Videos, Fotos, Kalender, formatierte Texte) ✅ Bewertungssystem (nur nach Buchung, Sterne + Text max 200 Zeichen, Moderation) ✅ Anfrage-System mit E-Mail-Benachrichtigung ✅ Suche nach Stil/Genre ✅ Admin-Backend (Freischaltung, Moderation) ✅ PayPal-Integration (aktivierbar) + Vermittlungsgebühr ✅ DSGVO (Cookie-Banner, AGB, Datenschutz) Dateistruktur-Plan (mehrere Files) text
getyourband/ ├── index.php # Startseite mit Band-Übersicht ├── band-detail.php # Band-Detailseite ├── anfrage.php # Anfrage-Formular ├── login.php # Login/Registrierung ├── verify-email.php # E-Mail-Verifizierung ├── profil.php # Band-Profil bearbeiten ├── admin/ │ ├── dashboard.php # Admin-Übersicht │ ├── bands.php # Bands freischalten │ ├── bewertungen.php # Bewertungen moderieren │ └── settings.php # Gebühren/PayPal aktivieren ├── includes/ │ ├── config.php # DB-Verbindung, Konstanten │ ├── functions.php # Hilfsfunktionen │ ├── auth.php # Login-Logik │ └── email.php # E-Mail-Versand ├── assets/ │ ├── css/style.css # Funky Design │ ├── js/app.js # JavaScript │ └── images/ ├── database.sql # SQL-Schema └── .htaccess # Apache-Konfiguration
Umsetzung in Schritten Ich werde folgende Schritte nacheinander liefern: Schritt 1: Datenbank-Schema + Basis-Setup
SQL-Tabellen (Bands, Users, Bewertungen, Anfragen)
config.php mit DB-Verbindung
.htaccess für Clean URLs
Schritt 2: Authentifizierung
Login/Registrierung
E-Mail-Verifizierung
Session-Management
Schritt 3: Frontend (Startseite + Band-Detail)
Funky Design mit gelben Tönen
Band-Übersicht mit Suche
Detailseite mit Kalender
Schritt 4: Anfrage-System
Anfrage-Formular
E-Mail-Benachrichtigung
Anfragen-Verwaltung
Schritt 5: Bewertungssystem
Bewertungen abgeben (nur nach Buchung)
Moderation im Admin-Bereich
Schritt 6: Admin-Backend
Dashboard
Freischaltung von Bands
PayPal/Gebühren-Einstellungen
Schritt 7: DSGVO & Feinschliff
Cookie-Banner
AGB/Datenschutz-Seiten
Mobile-Optimierung
Offene Frage vor Start: SQLite oder MySQL?
SQLite = keine Installation, einfacher Start, Limit bei vielen gleichzeitigen Zugriffen
MySQL = performanter, aber Installation nötig
Deine Wahl? Dann starte ich mit Schritt 1! 🎸🔥 Sources Images Videos News ubuntuusers.de Apache 2.4 › Wiki › ubuntuusers.de
Zu den Modulen zählt auch das Einbinden von PHP. Weiterführende Informationen sind im Artikel Apache/Module zu finden. Sollte /var/log/ nicht verfügbar sein (zum Beispiel durch ein temporäres Verzeichnis - siehe Tuning (Abschnitt „Temporaere-Dateien“)) wird ein Starten des Daemons abgebrochen. ubuntuusers.de PHP › Wiki › ubuntuusers.de
Nach einem Neuladen (Reload) des Apache-Servers sollte nun die neu installierte PHP-Version verfügbar sein. Zu installierende PHP-Erweiterungen müssen, wenn man diese Paketquelle benutzt, mit der installierten PHP-Version angegeben werden, zum Beispiel sudo apt install php8.0-intl. Ansonsten wird nicht nur die Erweiterung aus den Ubuntu-eigenen Paketquellen installiert (main), es wird auch PHP komplett neu aus den Ubuntu-Paketquellen installiert! ubuntuusers.de Serverinstallation › Archiv › Wiki › ubuntuusers.de
Ab Ubuntu Breezy 5.10 ist es auch möglich php5 anstatt von php4 zu installieren. Die oben angegebene Konfiguration ist ganz sicher nicht die einzig mögliche (bzw. einzig richtige) Auswahl. Hier wird konkret ein FTP-Server installiert (Zweck: Datenübertragung), ein SSH-Server (Zweck: Fernsteuerung des Servers von einem anderen Computer, z.B. mit puTTY), Apache als Webserver, hier in einer sehr stabilen (aber relativ langsamen) Variante, die mit MediaWiki-Software (Wikipedia) garantiert funktioniert. digitalocean.com Ausführen mehrerer PHP-Versionen auf einem Server unter Verwendung von Apache und PHP-FPM unter Ubuntu 18.04 | DigitalOcean
site1.your_domain> Options Indexes FollowSymLinks MultiViews AllowOverride All Order allow,deny allow from all <FilesMatch .php$> # For Apache version 2.4.10 and above, use SetHandler to run PHP as a fastCGI process server SetHandler "proxy:unix:/run/php/ sqlite. lets go Workflow
Thinking
Searching
Thinking
Responding 🎸 SCHRITT 1: Datenbank-Schema + Basis-Setup Los geht's! Hier ist die Foundation für getyourband.ch: 📁 Datei 1: database.sql sql
-- SQLite Datenbank-Schema für getyourband.ch -- Erstellt: 2025-11-16
-- Benutzer-Tabelle (Bands + Veranstalter) CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, user_type VARCHAR(20) NOT NULL CHECK(user_type IN ('band', 'veranstalter')), is_verified INTEGER DEFAULT 0, verification_token VARCHAR(64), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, last_login DATETIME );
-- Band-Profile CREATE TABLE bands ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, band_name VARCHAR(255) NOT NULL, genre VARCHAR(100), description TEXT, location VARCHAR(255), price_range VARCHAR(50), phone VARCHAR(50), website VARCHAR(255), is_approved INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE );
-- Band-Medien (Fotos/Videos) CREATE TABLE band_media ( id INTEGER PRIMARY KEY AUTOINCREMENT, band_id INTEGER NOT NULL, media_type VARCHAR(10) CHECK(media_type IN ('photo', 'video')), file_path VARCHAR(500), video_url VARCHAR(500), display_order INTEGER DEFAULT 0, uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (band_id) REFERENCES bands(id) ON DELETE CASCADE );
-- Verfügbarkeitskalender CREATE TABLE availability ( id INTEGER PRIMARY KEY AUTOINCREMENT, band_id INTEGER NOT NULL, date DATE NOT NULL, is_available INTEGER DEFAULT 1, notes TEXT, FOREIGN KEY (band_id) REFERENCES bands(id) ON DELETE CASCADE, UNIQUE(band_id, date) );
-- Anfragen CREATE TABLE anfragen ( id INTEGER PRIMARY KEY AUTOINCREMENT, band_id INTEGER NOT NULL, veranstalter_id INTEGER, veranstalter_name VARCHAR(255) NOT NULL, veranstalter_email VARCHAR(255) NOT NULL, veranstalter_phone VARCHAR(50), event_date DATE NOT NULL, event_location VARCHAR(255), event_type VARCHAR(100), budget VARCHAR(50), message TEXT, status VARCHAR(20) DEFAULT 'pending' CHECK(status IN ('pending', 'accepted', 'declined', 'completed')), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (band_id) REFERENCES bands(id) ON DELETE CASCADE, FOREIGN KEY (veranstalter_id) REFERENCES users(id) ON DELETE SET NULL );
-- Bewertungen (nur nach abgeschlossener Buchung) CREATE TABLE bewertungen ( id INTEGER PRIMARY KEY AUTOINCREMENT, band_id INTEGER NOT NULL, anfrage_id INTEGER NOT NULL, veranstalter_id INTEGER NOT NULL, rating INTEGER CHECK(rating >= 1 AND rating <= 5), comment TEXT CHECK(LENGTH(comment) <= 200), is_approved INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (band_id) REFERENCES bands(id) ON DELETE CASCADE, FOREIGN KEY (anfrage_id) REFERENCES anfragen(id) ON DELETE CASCADE, FOREIGN KEY (veranstalter_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE(anfrage_id) );
-- System-Einstellungen (PayPal, Gebühren) CREATE TABLE settings ( id INTEGER PRIMARY KEY AUTOINCREMENT, setting_key VARCHAR(100) UNIQUE NOT NULL, setting_value TEXT, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP );
-- Standard-Einstellungen einfügen INSERT INTO settings (setting_key, setting_value) VALUES ('paypal_enabled', '0'), ('paypal_client_id', ''), ('vermittlungsgebuehr_prozent', '10'), ('site_email', 'info@getyourband.ch');
-- Indizes für Performance CREATE INDEX idx_bands_approved ON bands(is_approved); CREATE INDEX idx_bands_genre ON bands(genre); CREATE INDEX idx_anfragen_status ON anfragen(status); CREATE INDEX idx_bewertungen_approved ON bewertungen(is_approved); CREATE INDEX idx_users_email ON users(email);
📁 Datei 2: includes/config.php php
<?php // Konfigurationsdatei für getyourband.ch // Erstellt: 2025-11-16 // Fehlerbehandlung (Entwicklung) error_reporting(E_ALL); ini_set('display_errors', 1); // Zeitzone date_default_timezone_set('Europe/Zurich'); // Basis-Pfade define('BASE_PATH', dirname(__DIR__)); define('BASE_URL', 'http://localhost/getyourband'); // Anpassen für Produktion // Datenbank-Konfiguration (SQLite) define('DB_PATH', BASE_PATH . '/database/getyourband.db'); // Session-Konfiguration ini_set('session.cookie_httponly', 1); ini_set('session.use_strict_mode', 1); session_start(); // Datenbank-Verbindung function getDB() { try { $db = new PDO('sqlite:' . DB_PATH); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Foreign Keys aktivieren (wichtig für SQLite) $db->exec('PRAGMA foreign_keys = ON;'); return $db; } catch(PDOException $e) { die('Datenbankverbindung fehlgeschlagen: ' . $e->getMessage()); } } // E-Mail-Konfiguration define('SMTP_HOST', 'smtp.gmail.com'); // Anpassen define('SMTP_PORT', 587); define('SMTP_USER', 'deine-email@gmail.com'); // Anpassen define('SMTP_PASS', 'dein-app-passwort'); // Anpassen define('FROM_EMAIL', 'noreply@getyourband.ch'); define('FROM_NAME', 'GetYourBand.ch'); // Upload-Konfiguration define('UPLOAD_PATH', BASE_PATH . '/uploads/'); define('MAX_FILE_SIZE', 10 * 1024 * 1024); // 10MB define('ALLOWED_IMAGE_TYPES', ['image/jpeg', 'image/png', 'image/webp']); // Sicherheit define('PASSWORD_MIN_LENGTH', 8); define('TOKEN_LENGTH', 32); // Hilfsfunktionen function isLoggedIn() { return isset($_SESSION['user_id']); } function isBand() { return isset($_SESSION['user_type']) && $_SESSION['user_type'] === 'band'; } function isVeranstalter() { return isset($_SESSION['user_type']) && $_SESSION['user_type'] === 'veranstalter'; } function isAdmin() { return isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true; } function redirect($url) { header('Location: ' . BASE_URL . $url); exit; } function sanitize($data) { return htmlspecialchars(trim($data), ENT_QUOTES, 'UTF-8'); } function generateToken($length = TOKEN_LENGTH) { return bin2hex(random_bytes($length)); } ?>📁 Datei 3: .htaccess apache
Apache-Konfiguration für getyourband.ch
PHP-Einstellungen
php_flag display_errors On php_value upload_max_filesize 10M php_value post_max_size 12M
Sicherheit
Options -Indexes ServerSignature Off
Clean URLs
RewriteEngine On RewriteBase /getyourband/
Weiterleitung zu HTTPS (für Produktion)
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Datei-Schutz
<FilesMatch ".(sql|db|log)$"> Order allow,deny Deny from all
Clean URL Regeln
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^band/([0-9]+)$ band-detail.php?id=$1 [L,QSA] RewriteRule ^admin/(.*)$ admin/$1 [L,QSA]
📁 Datei 4: setup.php (Einmalig ausführen) php
<?php // Setup-Script für initiale Datenbank-Erstellung // Nach Ausführung LÖSCHEN oder umbenennen! require_once 'includes/config.php'; echo "🎸 GetYourBand.ch Setup
"; // Datenbank-Ordner erstellen $dbDir = BASE_PATH . '/database'; if (!file_exists($dbDir)) { mkdir($dbDir, 0755, true); echo "✅ Datenbank-Ordner erstellt"; } // Upload-Ordner erstellen $uploadDirs = [ UPLOAD_PATH, UPLOAD_PATH . 'bands/', UPLOAD_PATH . 'photos/', UPLOAD_PATH . 'videos/' ]; foreach ($uploadDirs as $dir) { if (!file_exists($dir)) { mkdir($dir, 0755, true); echo "✅ Upload-Ordner erstellt: $dir
"; } } // SQL-Schema laden und ausführen $sql = file_get_contents(BASE_PATH . '/database.sql'); try { $db = getDB(); $db->exec($sql); echo "✅ Datenbank-Schema erfolgreich erstellt
"; // Test-Admin erstellen $adminEmail = 'admin@getyourband.ch'; $adminPassword = password_hash('Admin123!', PASSWORD_DEFAULT); $stmt = $db->prepare("INSERT INTO users (email, password_hash, user_type, is_verified) VALUES (?, ?, 'band', 1)"); $stmt->execute([$adminEmail, $adminPassword]); echo "✅ Test-Admin erstellt: $adminEmail / Admin123!
"; echo "
"; } catch(PDOException $e) { echo "❌ Fehler: " . $e->getMessage(); } ?>
📋 Installation - Schritt für Schritt
Ordnerstruktur erstellen:
text
getyourband/ ├── database/ (wird automatisch erstellt) ├── uploads/ (wird automatisch erstellt) ├── includes/ │ └── config.php ├── database.sql ├── setup.php └── .htaccess
config.php anpassen:
BASE_URL auf deine Domain setzen
E-Mail-Einstellungen konfigurieren
setup.php im Browser aufrufen:
http://deine-domain.ch/getyourband/setup.php
Datenbank wird automatisch erstellt
setup.php löschen!
✅ Was ist jetzt fertig?
✅ Komplettes Datenbank-Schema (SQLite)
✅ Basis-Konfiguration mit Sicherheitsfunktionen
✅ Clean URLs via .htaccess
✅ Automatisches Setup-Script
🚀 Nächster Schritt? Sag Bescheid, dann liefere ich Schritt 2: Authentifizierung (Login/Registrierung/E-Mail-Verifizierung)! 🎸 Sources Images Videos News sqlite.org SQLite Home Page
SQLite is a C-language library that implements a small, fast, self-contained, high-reliability, full-featured, SQL database engine. SQLite is the most used database engine in the world. SQLite is built into all mobile phones and most computers and comes bundled inside countless other applications ... wikipedia.org SQLite - Wikipedia
SQLite (/ˌɛsˌkjuːˌɛlˈaɪt/ "S-Q-L-ite", /ˈsiːkwəˌlaɪt/ "sequel-ite") is a free and open-source relational database engine written in the C programming language. It is not a standalone app; rather, it is a library that software developers embed in their apps. sqlitebrowser.org DB Browser for SQLite
DB Browser for SQLite (DB4S) is a high quality, visual, open source tool designed for people who want to create, search, and edit SQLite or SQLCipher database files. DB4S gives a familiar spreadsheet-like interface on the database in addition to providing a full SQL query facility. python.org sqlite3 — DB-API 2.0 interface for SQLite databases
SQLite is a C library that provides a lightweight disk-based database that doesn’t require a separate server process and allows accessing the database using a nonstandard variant of the SQL query language. weiter Workflow
Thinking
Searching
Thinking
Responding 🎸 SCHRITT 2: Authentifizierung (Login/Registrierung/E-Mail-Verifizierung) Jetzt bauen wir das komplette Auth-System! 📁 Datei 5: includes/auth.php php
<?php // Authentifizierungs-Funktionen für getyourband.ch require_once 'config.php'; require_once 'email.php'; class Auth { private $db; public function __construct() { $this->db = getDB(); } /** * Benutzer registrieren */ public function register($email, $password, $userType) { // Validierung if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { return ['success' => false, 'error' => 'Ungültige E-Mail-Adresse']; } if (strlen($password) < PASSWORD_MIN_LENGTH) { return ['success' => false, 'error' => 'Passwort muss mindestens ' . PASSWORD_MIN_LENGTH . ' Zeichen lang sein']; } if (!in_array($userType, ['band', 'veranstalter'])) { return ['success' => false, 'error' => 'Ungültiger Benutzertyp']; } // E-Mail bereits registriert? $stmt = $this->db->prepare("SELECT id FROM users WHERE email = ?"); $stmt->execute([$email]); if ($stmt->fetch()) { return ['success' => false, 'error' => 'E-Mail-Adresse bereits registriert']; } // Benutzer erstellen $passwordHash = password_hash($password, PASSWORD_DEFAULT); $verificationToken = generateToken(); try { $stmt = $this->db->prepare(" INSERT INTO users (email, password_hash, user_type, verification_token) VALUES (?, ?, ?, ?) "); $stmt->execute([$email, $passwordHash, $userType, $verificationToken]); $userId = $this->db->lastInsertId(); // Verifizierungs-E-Mail senden $verificationLink = BASE_URL . '/verify-email.php?token=' . $verificationToken; $emailSent = sendVerificationEmail($email, $verificationLink); return [ 'success' => true, 'user_id' => $userId, 'email_sent' => $emailSent ]; } catch(PDOException $e) { return ['success' => false, 'error' => 'Registrierung fehlgeschlagen: ' . $e->getMessage()]; } } /** * Benutzer einloggen */ public function login($email, $password) { $stmt = $this->db->prepare(" SELECT id, email, password_hash, user_type, is_verified FROM users WHERE email = ? "); $stmt->execute([$email]); $user = $stmt->fetch(); if (!$user) { return ['success' => false, 'error' => 'E-Mail oder Passwort falsch']; } if (!password_verify($password, $user['password_hash'])) { return ['success' => false, 'error' => 'E-Mail oder Passwort falsch']; } if (!$user['is_verified']) { return ['success' => false, 'error' => 'Bitte verifiziere zuerst deine E-Mail-Adresse']; } // Session erstellen session_regenerate_id(true); $_SESSION['user_id'] = $user['id']; $_SESSION['email'] = $user['email']; $_SESSION['user_type'] = $user['user_type']; // Admin-Check (erste User = Admin) if ($user['id'] == 1) { $_SESSION['is_admin'] = true; } // Last Login aktualisieren $stmt = $this->db->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?"); $stmt->execute([$user['id']]); return ['success' => true, 'user_type' => $user['user_type']]; } /** * E-Mail verifizieren */ public function verifyEmail($token) { $stmt = $this->db->prepare(" SELECT id FROM users WHERE verification_token = ? AND is_verified = 0 "); $stmt->execute([$token]); $user = $stmt->fetch(); if (!$user) { return ['success' => false, 'error' => 'Ungültiger oder bereits verwendeter Verifizierungslink']; } $stmt = $this->db->prepare(" UPDATE users SET is_verified = 1, verification_token = NULL WHERE id = ? "); $stmt->execute([$user['id']]); return ['success' => true]; } /** * Ausloggen */ public function logout() { session_unset(); session_destroy(); session_start(); } /** * Passwort zurücksetzen - Token generieren */ public function requestPasswordReset($email) { $stmt = $this->db->prepare("SELECT id FROM users WHERE email = ?"); $stmt->execute([$email]); $user = $stmt->fetch(); if (!$user) { // Aus Sicherheitsgründen trotzdem Success zurückgeben return ['success' => true]; } $resetToken = generateToken(); $stmt = $this->db->prepare(" UPDATE users SET verification_token = ? WHERE id = ? "); $stmt->execute([$resetToken, $user['id']]); $resetLink = BASE_URL . '/reset-password.php?token=' . $resetToken; sendPasswordResetEmail($email, $resetLink); return ['success' => true]; } /** * Passwort zurücksetzen - Neues Passwort setzen */ public function resetPassword($token, $newPassword) { if (strlen($newPassword) < PASSWORD_MIN_LENGTH) { return ['success' => false, 'error' => 'Passwort zu kurz']; } $stmt = $this->db->prepare(" SELECT id FROM users WHERE verification_token = ? "); $stmt->execute([$token]); $user = $stmt->fetch(); if (!$user) { return ['success' => false, 'error' => 'Ungültiger Link']; } $passwordHash = password_hash($newPassword, PASSWORD_DEFAULT); $stmt = $this->db->prepare(" UPDATE users SET password_hash = ?, verification_token = NULL WHERE id = ? "); $stmt->execute([$passwordHash, $user['id']]); return ['success' => true]; } } ?>📁 Datei 6: includes/email.php php
<?php // E-Mail-Funktionen für getyourband.ch require_once 'config.php'; /** * Basis-E-Mail-Funktion (verwendet PHP mail()) * Für Produktion: PHPMailer oder SMTP verwenden! */ function sendEmail($to, $subject, $htmlBody) { $headers = "MIME-Version: 1.0" . "\r\n"; $headers .= "Content-type:text/html;charset=UTF-8" . "\r\n"; $headers .= "From: " . FROM_NAME . " <" . FROM_EMAIL . ">" . "\r\n"; return mail($to, $subject, $htmlBody, $headers); } /** * Verifizierungs-E-Mail senden */ function sendVerificationEmail($email, $verificationLink) { $subject = "🎸 Bestätige deine E-Mail-Adresse bei GetYourBand.ch"; $body = getEmailTemplate("Willkommen bei GetYourBand.ch!
Vielen Dank für deine Registrierung. Bitte bestätige deine E-Mail-Adresse, um deinen Account zu aktivieren:
Falls der Button nicht funktioniert, kopiere diesen Link in deinen Browser:
$verificationLink
Passwort zurücksetzen
Du hast eine Anfrage zum Zurücksetzen deines Passworts gestellt. Klicke auf den Button, um ein neues Passwort zu vergeben:
Falls du diese Anfrage nicht gestellt hast, ignoriere diese E-Mail einfach.
Link: $resetLink
Neue Buchungsanfrage!
$veranstalterName möchte eure Band für einen Event buchen:
- Datum: $eventDate
📁 Datei 7: login.php php
<?php require_once 'includes/config.php'; require_once 'includes/auth.php'; $auth = new Auth(); $error = ''; $success = ''; $mode = $_GET['mode'] ?? 'login'; // login, register, forgot // Login-Verarbeitung if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) { $result = $auth->login($_POST['email'], $_POST['password']); if ($result['success']) { redirect($result['user_type'] === 'band' ? '/profil.php' : '/index.php'); } else { $error = $result['error']; } } // Registrierungs-Verarbeitung if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['register'])) { $result = $auth->register($_POST['email'], $_POST['password'], $_POST['user_type']); if ($result['success']) { $success = 'Registrierung erfolgreich! Bitte überprüfe deine E-Mails zur Verifizierung.'; $mode = 'login'; } else { $error = $result['error']; } } // Passwort vergessen if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['forgot'])) { $result = $auth->requestPasswordReset($_POST['email']); $success = 'Falls die E-Mail-Adresse existiert, wurde ein Reset-Link versendet.'; } ?> <html lang="de"> <head> </head>🎸 GetYourBand.ch
Die funky Bandvermittlung
<?php if ($error): ?>
<div class="alert alert-error"><?= sanitize($error) ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success"><?= sanitize($success) ?></div>
<?php endif; ?>
<div class="auth-tabs">
<button class="tab-btn <?= $mode === 'login' ? 'active' : '' ?>" onclick="switchMode('login')">Login</button>
<button class="tab-btn <?= $mode === 'register' ? 'active' : '' ?>" onclick="switchMode('register')">Registrierung</button>
</div>
<!-- Login-Formular -->
<form method="POST" class="auth-form <?= $mode === 'login' ? 'active' : '' ?>" id="login-form">
<div class="form-group">
<label>E-Mail</label>
<input type="email" name="email" required>
</div>
<div class="form-group">
<label>Passwort</label>
<input type="password" name="password" required>
</div>
<button type="submit" name="login" class="btn btn-primary">Einloggen</button>
<p class="form-footer">
<a href="?mode=forgot">Passwort vergessen?</a>
</p>
</form>
<!-- Registrierungs-Formular -->
<form method="POST" class="auth-form <?= $mode === 'register' ? 'active' : '' ?>" id="register-form">
<div class="form-group">
<label>Ich bin...</label>
<select name="user_type" required>
<option value="band">🎸 Band (möchte gebucht werden)</option>
<option value="veranstalter">🎉 Veranstalter (möchte Band buchen)</option>
</select>
</div>
<div class="form-group">
<label>E-Mail</label>
<input type="email" name="email" required>
</div>
<div class="form-group">
<label>Passwort (mind. 8 Zeichen)</label>
<input type="password" name="password" minlength="8" required>
</div>
<div class="form-group checkbox">
<label>
<input type="checkbox" required>
Ich akzeptiere die <a href="agb.php" target="_blank">AGB</a> und <a href="datenschutz.php" target="_blank">Datenschutzerklärung</a>
</label>
</div>
<button type="submit" name="register" class="btn btn-primary">Registrieren</button>
</form>
<!-- Passwort vergessen -->
<form method="POST" class="auth-form <?= $mode === 'forgot' ? 'active' : '' ?>" id="forgot-form">
<p>Gib deine E-Mail-Adresse ein, um einen Reset-Link zu erhalten:</p>
<div class="form-group">
<label>E-Mail</label>
<input type="email" name="email" required>
</div>
<button type="submit" name="forgot" class="btn btn-primary">Link senden</button>
<p class="form-footer">
<a href="?mode=login">Zurück zum Login</a>
</p>
</form>
</div>
<script>
function switchMode(mode) {
window.location.href = '?mode=' + mode;
}
</script>
</html>
📁 Datei 8: verify-email.php php
<?php require_once 'includes/config.php'; require_once 'includes/auth.php'; $auth = new Auth(); $message = ''; $success = false; if (isset($_GET['token'])) { $result = $auth->verifyEmail($_GET['token']); if ($result['success']) { $success = true; $message = 'E-Mail erfolgreich verifiziert! Du kannst dich jetzt einloggen.'; } else { $message = $result['error']; } } else { $message = 'Kein Verifizierungstoken gefunden.'; } ?> <html lang="de"> <head> </head>🎸 GetYourBand.ch
<div class="alert <?= $success ? 'alert-success' : 'alert-error' ?>">
<?= sanitize($message) ?>
</div>
<?php if ($success): ?>
<a href="login.php" class="btn btn-primary">Zum Login</a>
<?php else: ?>
<a href="index.php" class="btn btn-secondary">Zur Startseite</a>
<?php endif; ?>
</div>
</html>
📁 Datei 9: reset-password.php php
<?php require_once 'includes/config.php'; require_once 'includes/auth.php'; $auth = new Auth(); $error = ''; $success = false; $token = $_GET['token'] ?? ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_POST['password'] !== $_POST['password_confirm']) { $error = 'Passwörter stimmen nicht überein'; } else { $result = $auth->resetPassword($token, $_POST['password']); if ($result['success']) { $success = true; } else { $error = $result['error']; } } } ?> <html lang="de"> <head> </head>🎸 GetYourBand.ch
Neues Passwort setzen
<?php if ($error): ?>
<div class="alert alert-error"><?= sanitize($error) ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success">
Passwort erfolgreich geändert! Du kannst dich jetzt einloggen.
</div>
<a href="login.php" class="btn btn-primary">Zum Login</a>
<?php else: ?>
<form method="POST" class="auth-form">
<div class="form-group">
<label>Neues Passwort (mind. 8 Zeichen)</label>
<input type="password" name="password" minlength="8" required>
</div>
<div class="form-group">
<label>Passwort bestätigen</label>
<input type="password" name="password_confirm" minlength="8" required>
</div>
<button type="submit" class="btn btn-primary">Passwort ändern</button>
</form>
<?php endif; ?>
</div>
</html>
✅ Was ist jetzt fertig?
✅ Komplettes Auth-System (Login/Registrierung/Logout)
✅ E-Mail-Verifizierung mit Token
✅ Passwort-Reset-Funktion
✅ Sichere Passwort-Hashes (bcrypt)
✅ Session-Management
✅ E-Mail-Templates (funky Design)
✅ Benutzertypen (Band/Veranstalter)
🚀 Nächster Schritt? Sag Bescheid für Schritt 3: Frontend (Startseite + Band-Detail mit funky Design)! 🎸✨ Sources Images Videos News html-seminar.de Einfache PHP & Datenbank (MySQL) basierte Benutzerverwaltung (Login/Logout/Registrierung) - AJAX Kompatibel - PHP - HTML-Seminar.de - Forum für HTML, CSS und PHP
Ziel: eine einfache Datenbank basierte Benutzerverwaltung erstellen Verwendete Techniken: PHP, PDO Schwierigkeitsgrad: für Anfänger geeignet, Grundkenntnisse über die Funktionsweisen von PHP und SQL vorausgesetzt Anmerkungen: verwendet PDO, wegen dem… tutsplus.com Die 20 besten Veranstaltungs- und Buchungskalender mit PHP-Scripts... und 3 kostenlose Optionen | Envato Tuts+
Es ist mit PayPal und Razorpay integriert. Der Vacation Rentals Booking Calendar ist einen Belegungsplan mit Buchungsystem, mit dem Vermietungsbetrieb oder Verwaltungsagenturen Kalender für die Verfügbarkeit ihrer Ferienunterkünfte erstellen und verwalten können. prepaid-hoster.de Server mit Apache, PHP, MySQL mieten - Prepaid Hoster
Webserver-Ready in wenigen Sekunden · Einfaches Webserver Hosting auf dem eigenen vServer zoechbauer.name Projekt Webserver – Ubuntu 22.04 Teil 3 – PHP und MySQL nachinstallieren und konfigurieren
Im Teil 2 dieser Serie haben wir einen funktionierenden Webserver für statische HTML-Webseiten erstellt. Jetzt erweitern wir den Funktionsumfang noch etwas ind weiter Workflow
Thinking
Searching
Thinking
Responding 🎸 SCHRITT 2: Authentifizierung (Login/Registrierung/E-Mail-Verifizierung) Jetzt bauen wir das komplette Auth-System! 📁 Datei 5: includes/auth.php php
<?php // Authentifizierungs-Funktionen für getyourband.ch require_once 'config.php'; require_once 'email.php'; class Auth { private $db; public function __construct() { $this->db = getDB(); } /** * Benutzer registrieren */ public function register($email, $password, $userType) { // Validierung if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { return ['success' => false, 'error' => 'Ungültige E-Mail-Adresse']; } if (strlen($password) < PASSWORD_MIN_LENGTH) { return ['success' => false, 'error' => 'Passwort muss mindestens ' . PASSWORD_MIN_LENGTH . ' Zeichen lang sein']; } if (!in_array($userType, ['band', 'veranstalter'])) { return ['success' => false, 'error' => 'Ungültiger Benutzertyp']; } // E-Mail bereits registriert? $stmt = $this->db->prepare("SELECT id FROM users WHERE email = ?"); $stmt->execute([$email]); if ($stmt->fetch()) { return ['success' => false, 'error' => 'E-Mail-Adresse bereits registriert']; } // Benutzer erstellen $passwordHash = password_hash($password, PASSWORD_DEFAULT); $verificationToken = generateToken(); try { $stmt = $this->db->prepare(" INSERT INTO users (email, password_hash, user_type, verification_token) VALUES (?, ?, ?, ?) "); $stmt->execute([$email, $passwordHash, $userType, $verificationToken]); $userId = $this->db->lastInsertId(); // Verifizierungs-E-Mail senden $verificationLink = BASE_URL . '/verify-email.php?token=' . $verificationToken; $emailSent = sendVerificationEmail($email, $verificationLink); return [ 'success' => true, 'user_id' => $userId, 'email_sent' => $emailSent ]; } catch(PDOException $e) { return ['success' => false, 'error' => 'Registrierung fehlgeschlagen: ' . $e->getMessage()]; } } /** * Benutzer einloggen */ public function login($email, $password) { $stmt = $this->db->prepare(" SELECT id, email, password_hash, user_type, is_verified FROM users WHERE email = ? "); $stmt->execute([$email]); $user = $stmt->fetch(); if (!$user) { return ['success' => false, 'error' => 'E-Mail oder Passwort falsch']; } if (!password_verify($password, $user['password_hash'])) { return ['success' => false, 'error' => 'E-Mail oder Passwort falsch']; } if (!$user['is_verified']) { return ['success' => false, 'error' => 'Bitte verifiziere zuerst deine E-Mail-Adresse']; } // Session erstellen $_SESSION['user_id'] = $user['id']; $_SESSION['email'] = $user['email']; $_SESSION['user_type'] = $user['user_type']; // Admin-Check (erste User = Admin) if ($user['id'] == 1) { $_SESSION['is_admin'] = true; } // Last Login aktualisieren $stmt = $this->db->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?"); $stmt->execute([$user['id']]); return ['success' => true, 'user_type' => $user['user_type']]; } /** * E-Mail verifizieren */ public function verifyEmail($token) { $stmt = $this->db->prepare(" SELECT id FROM users WHERE verification_token = ? AND is_verified = 0 "); $stmt->execute([$token]); $user = $stmt->fetch(); if (!$user) { return ['success' => false, 'error' => 'Ungültiger oder bereits verwendeter Token']; } $stmt = $this->db->prepare(" UPDATE users SET is_verified = 1, verification_token = NULL WHERE id = ? "); $stmt->execute([$user['id']]); return ['success' => true]; } /** * Ausloggen */ public function logout() { session_destroy(); session_start(); } /** * Passwort zurücksetzen - Token generieren */ public function requestPasswordReset($email) { $stmt = $this->db->prepare("SELECT id FROM users WHERE email = ?"); $stmt->execute([$email]); $user = $stmt->fetch(); if (!$user) { // Aus Sicherheitsgründen trotzdem Success zurückgeben return ['success' => true]; } $resetToken = generateToken(); $stmt = $this->db->prepare(" UPDATE users SET verification_token = ? WHERE id = ? "); $stmt->execute([$resetToken, $user['id']]); $resetLink = BASE_URL . '/reset-password.php?token=' . $resetToken; sendPasswordResetEmail($email, $resetLink); return ['success' => true]; } } ?>📁 Datei 6: includes/email.php php
<?php // E-Mail-Funktionen für getyourband.ch require_once 'config.php'; /** * Basis-E-Mail-Funktion (verwendet PHP mail()) * Für Produktion: PHPMailer oder SMTP verwenden */ function sendEmail($to, $subject, $htmlBody) { $headers = "MIME-Version: 1.0" . "\r\n"; $headers .= "Content-type:text/html;charset=UTF-8" . "\r\n"; $headers .= "From: " . FROM_NAME . " <" . FROM_EMAIL . ">" . "\r\n"; return mail($to, $subject, $htmlBody, $headers); } /** * Verifizierungs-E-Mail senden */ function sendVerificationEmail($email, $verificationLink) { $subject = "🎸 Bestätige deine E-Mail bei GetYourBand.ch"; $body = " <html> <head> <style> body { font-family: Arial, sans-serif; background: #1a1a1a; color: #fff; } .container { max-width: 600px; margin: 0 auto; padding: 40px 20px; } .header { background: linear-gradient(135deg, #FFD700, #FFA500); padding: 30px; text-align: center; border-radius: 10px 10px 0 0; } .content { background: #2a2a2a; padding: 30px; border-radius: 0 0 10px 10px; } .button { display: inline-block; background: #FFD700; color: #000; padding: 15px 40px; text-decoration: none; border-radius: 5px; font-weight: bold; margin: 20px 0; } .footer { text-align: center; margin-top: 30px; color: #888; font-size: 12px; } </style> </head>🎸 GetYourBand.ch
Willkommen bei GetYourBand.ch!
Danke für deine Registrierung. Bitte bestätige deine E-Mail-Adresse, um deinen Account zu aktivieren:
Oder kopiere diesen Link in deinen Browser:
$verificationLink
Der Link ist 24 Stunden gültig.
🎸 GetYourBand.ch
Passwort zurücksetzen
Du hast eine Passwort-Zurücksetzung angefordert. Klicke auf den Button, um ein neues Passwort zu setzen:
Falls du diese Anfrage nicht gestellt hast, ignoriere diese E-Mail einfach.
🎸 GetYourBand.ch
📁 Datei 7: login.php php
<?php require_once 'includes/config.php'; require_once 'includes/auth.php'; $auth = new Auth(); $error = ''; $success = ''; // Login-Verarbeitung if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) { $result = $auth->login($_POST['email'], $_POST['password']); if ($result['success']) { if ($result['user_type'] === 'band') { redirect('/profil.php'); } else { redirect('/index.php'); } } else { $error = $result['error']; } } // Registrierungs-Verarbeitung if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['register'])) { $result = $auth->register($_POST['email'], $_POST['password'], $_POST['user_type']); if ($result['success']) { $success = 'Registrierung erfolgreich! Bitte überprüfe deine E-Mails zur Verifizierung.'; } else { $error = $result['error']; } } ?> <html lang="de"> <head> </head>🎸 GetYourBand.ch
Die funky Bandvermittlung
<?php if ($error): ?>
<div class="alert alert-error"><?= sanitize($error) ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success"><?= sanitize($success) ?></div>
<?php endif; ?>
<div class="auth-tabs">
<button class="tab-btn active" data-tab="login">Login</button>
<button class="tab-btn" data-tab="register">Registrieren</button>
</div>
<!-- Login-Formular -->
<div id="login-tab" class="tab-content active">
<form method="POST" class="auth-form">
<div class="form-group">
<label for="login-email">E-Mail</label>
<input type="email" id="login-email" name="email" required>
</div>
<div class="form-group">
<label for="login-password">Passwort</label>
<input type="password" id="login-password" name="password" required>
</div>
<button type="submit" name="login" class="btn btn-primary">Einloggen</button>
<p class="form-footer">
<a href="reset-password.php">Passwort vergessen?</a>
</p>
</form>
</div>
<!-- Registrierungs-Formular -->
<div id="register-tab" class="tab-content">
<form method="POST" class="auth-form">
<div class="form-group">
<label for="reg-email">E-Mail</label>
<input type="email" id="reg-email" name="email" required>
</div>
<div class="form-group">
<label for="reg-password">Passwort (min. 8 Zeichen)</label>
<input type="password" id="reg-password" name="password" minlength="8" required>
</div>
<div class="form-group">
<label>Ich bin...</label>
<div class="radio-group">
<label class="radio-label">
<input type="radio" name="user_type" value="band" required>
<span>🎸 Band / Musiker</span>
</label>
<label class="radio-label">
<input type="radio" name="user_type" value="veranstalter" required>
<span>🎉 Veranstalter</span>
</label>
</div>
</div>
<button type="submit" name="register" class="btn btn-primary">Registrieren</button>
<p class="form-footer">
Mit der Registrierung akzeptierst du unsere <a href="agb.php">AGB</a> und <a href="datenschutz.php">Datenschutzerklärung</a>.
</p>
</form>
</div>
<div class="auth-footer">
<a href="index.php">← Zurück zur Startseite</a>
</div>
</div>
<script src="assets/js/auth.js"></script>
</html>
📁 Datei 8: verify-email.php php
<?php require_once 'includes/config.php'; require_once 'includes/auth.php'; $auth = new Auth(); $message = ''; $success = false; if (isset($_GET['token'])) { $result = $auth->verifyEmail($_GET['token']); if ($result['success']) { $success = true; $message = 'E-Mail erfolgreich verifiziert! Du kannst dich jetzt einloggen.'; } else { $message = $result['error']; } } else { $message = 'Kein Verifizierungs-Token gefunden.'; } ?> <html lang="de"> <head> </head>🎸 GetYourBand.ch
<div class="verification-result">
<?php if ($success): ?>
<div class="success-icon">✅</div>
<h2>Verifizierung erfolgreich!</h2>
<p><?= sanitize($message) ?></p>
<a href="login.php" class="btn btn-primary">Zum Login</a>
<?php else: ?>
<div class="error-icon">❌</div>
<h2>Verifizierung fehlgeschlagen</h2>
<p><?= sanitize($message) ?></p>
<a href="login.php" class="btn btn-secondary">Zurück zum Login</a>
<?php endif; ?>
</div>
</div>
</html>
📁 Datei 9: assets/js/auth.js javascript
// Tab-Switching für Login/Registrierung document.addEventListener('DOMContentLoaded', function() { const tabButtons = document.querySelectorAll('.tab-btn'); const tabContents = document.querySelectorAll('.tab-content');
tabButtons.forEach(button => {
button.addEventListener('click', function() {
const targetTab = this.dataset.tab;
// Alle Tabs deaktivieren
tabButtons.forEach(btn => btn.classList.remove('active'));
tabContents.forEach(content => content.classList.remove('active'));
// Aktiven Tab aktivieren
this.classList.add('active');
document.getElementById(targetTab + '-tab').classList.add('active');
});
});
// Passwort-Stärke-Anzeige
const passwordInput = document.getElementById('reg-password');
if (passwordInput) {
passwordInput.addEventListener('input', function() {
const strength = calculatePasswordStrength(this.value);
// Hier könnte eine visuelle Anzeige ergänzt werden
});
}
});
function calculatePasswordStrength(password) { let strength = 0; if (password.length >= 8) strength++; if (password.match(/[a-z]/)) strength++; if (password.match(/[A-Z]/)) strength++; if (password.match(/[0-9]/)) strength++; if (password.match(/[^a-zA-Z0-9]/)) strength++; return strength; }
✅ Was ist jetzt fertig?
✅ Komplettes Auth-System (Registrierung, Login, Logout)
✅ E-Mail-Verifizierung mit Token
✅ Passwort-Hashing (bcrypt)
✅ Session-Management
✅ E-Mail-Templates (HTML)
✅ Benutzertypen (Band / Veranstalter)
✅ Admin-Erkennung (User ID 1)
🚀 Nächster Schritt? Sag Bescheid für Schritt 3: Frontend (Funky Design + Startseite + Band-Übersicht)! 🎨✨ Sources Images Videos News html-seminar.de Einfache PHP & Datenbank (MySQL) basierte Benutzerverwaltung (Login/Logout/Registrierung) - AJAX Kompatibel - PHP - HTML-Seminar.de - Forum für HTML, CSS und PHP
Ziel: eine einfache Datenbank basierte Benutzerverwaltung erstellen Verwendete Techniken: PHP, PDO Schwierigkeitsgrad: für Anfänger geeignet, Grundkenntnisse über die Funktionsweisen von PHP und SQL vorausgesetzt Anmerkungen: verwendet PDO, wegen dem… tutsplus.com Die 20 besten Veranstaltungs- und Buchungskalender mit PHP-Scripts... und 3 kostenlose Optionen | Envato Tuts+
Es ist mit PayPal und Razorpay integriert. Der Vacation Rentals Booking Calendar ist einen Belegungsplan mit Buchungsystem, mit dem Vermietungsbetrieb oder Verwaltungsagenturen Kalender für die Verfügbarkeit ihrer Ferienunterkünfte erstellen und verwalten können. prepaid-hoster.de Server mit Apache, PHP, MySQL mieten - Prepaid Hoster
Webserver-Ready in wenigen Sekunden · Einfaches Webserver Hosting auf dem eigenen vServer zoechbauer.name Projekt Webserver – Ubuntu 22.04 Teil 3 – PHP und MySQL nachinstallieren und konfigurieren
Im Teil 2 dieser Serie haben wir einen funktionierenden Webserver für statische HTML-Webseiten erstellt. Jetzt erweitern wir den Funktionsumfang noch etwas ind weiter Workflow
Thinking
Searching
Thinking
Responding 🎸 SCHRITT 2: Authentifizierung (Login/Registrierung/E-Mail-Verifizierung) Jetzt bauen wir das komplette Auth-System! 📁 Datei 5: includes/auth.php php
<?php // Authentifizierungs-Funktionen für getyourband.ch require_once 'config.php'; require_once 'email.php'; class Auth { private $db; public function __construct() { $this->db = getDB(); } /** * Benutzer registrieren */ public function register($email, $password, $userType) { // Validierung if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { return ['success' => false, 'error' => 'Ungültige E-Mail-Adresse']; } if (strlen($password) < PASSWORD_MIN_LENGTH) { return ['success' => false, 'error' => 'Passwort muss mindestens ' . PASSWORD_MIN_LENGTH . ' Zeichen lang sein']; } if (!in_array($userType, ['band', 'veranstalter'])) { return ['success' => false, 'error' => 'Ungültiger Benutzertyp']; } // E-Mail bereits registriert? $stmt = $this->db->prepare("SELECT id FROM users WHERE email = ?"); $stmt->execute([$email]); if ($stmt->fetch()) { return ['success' => false, 'error' => 'E-Mail-Adresse bereits registriert']; } // Benutzer erstellen $passwordHash = password_hash($password, PASSWORD_DEFAULT); $verificationToken = generateToken(); try { $stmt = $this->db->prepare(" INSERT INTO users (email, password_hash, user_type, verification_token) VALUES (?, ?, ?, ?) "); $stmt->execute([$email, $passwordHash, $userType, $verificationToken]); $userId = $this->db->lastInsertId(); // Verifizierungs-E-Mail senden $verifyLink = BASE_URL . '/verify-email.php?token=' . $verificationToken; $emailSent = sendVerificationEmail($email, $verifyLink); return [ 'success' => true, 'user_id' => $userId, 'email_sent' => $emailSent ]; } catch(PDOException $e) { return ['success' => false, 'error' => 'Registrierung fehlgeschlagen: ' . $e->getMessage()]; } } /** * Benutzer einloggen */ public function login($email, $password) { $stmt = $this->db->prepare(" SELECT id, email, password_hash, user_type, is_verified FROM users WHERE email = ? "); $stmt->execute([$email]); $user = $stmt->fetch(); if (!$user) { return ['success' => false, 'error' => 'E-Mail oder Passwort falsch']; } if (!password_verify($password, $user['password_hash'])) { return ['success' => false, 'error' => 'E-Mail oder Passwort falsch']; } if (!$user['is_verified']) { return ['success' => false, 'error' => 'Bitte verifiziere zuerst deine E-Mail-Adresse']; } // Session erstellen $_SESSION['user_id'] = $user['id']; $_SESSION['email'] = $user['email']; $_SESSION['user_type'] = $user['user_type']; // Admin-Check (erste User = Admin) if ($user['id'] == 1) { $_SESSION['is_admin'] = true; } // Last Login aktualisieren $stmt = $this->db->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?"); $stmt->execute([$user['id']]); return ['success' => true, 'user_type' => $user['user_type']]; } /** * E-Mail verifizieren */ public function verifyEmail($token) { $stmt = $this->db->prepare(" SELECT id FROM users WHERE verification_token = ? AND is_verified = 0 "); $stmt->execute([$token]); $user = $stmt->fetch(); if (!$user) { return ['success' => false, 'error' => 'Ungültiger oder bereits verwendeter Token']; } $stmt = $this->db->prepare(" UPDATE users SET is_verified = 1, verification_token = NULL WHERE id = ? "); $stmt->execute([$user['id']]); return ['success' => true]; } /** * Ausloggen */ public function logout() { session_destroy(); session_start(); } /** * Passwort zurücksetzen - Token generieren */ public function requestPasswordReset($email) { $stmt = $this->db->prepare("SELECT id FROM users WHERE email = ?"); $stmt->execute([$email]); $user = $stmt->fetch(); if (!$user) { // Aus Sicherheitsgründen trotzdem Success zurückgeben return ['success' => true]; } $resetToken = generateToken(); $stmt = $this->db->prepare(" UPDATE users SET verification_token = ? WHERE id = ? "); $stmt->execute([$resetToken, $user['id']]); $resetLink = BASE_URL . '/reset-password.php?token=' . $resetToken; sendPasswordResetEmail($email, $resetLink); return ['success' => true]; } } ?>📁 Datei 6: includes/email.php php
<?php // E-Mail-Funktionen für getyourband.ch require_once 'config.php'; /** * Basis-E-Mail-Funktion (verwendet PHP mail()) * Für Produktion: PHPMailer oder SMTP verwenden */ function sendEmail($to, $subject, $htmlBody) { $headers = [ 'MIME-Version: 1.0', 'Content-type: text/html; charset=UTF-8', 'From: ' . FROM_NAME . ' <' . FROM_EMAIL . '>', 'Reply-To: ' . FROM_EMAIL, 'X-Mailer: PHP/' . phpversion() ]; $success = mail($to, $subject, $htmlBody, implode("\r\n", $headers)); return $success; } /** * Verifizierungs-E-Mail senden */ function sendVerificationEmail($email, $verifyLink) { $subject = '🎸 Bestätige deine E-Mail-Adresse bei GetYourBand.ch'; $html = ' <html> <head> <style> body { font-family: Arial, sans-serif; background: #1a1a1a; color: #fff; } .container { max-width: 600px; margin: 0 auto; padding: 40px 20px; } .header { background: linear-gradient(135deg, #FFD700, #FFA500); padding: 30px; text-align: center; border-radius: 10px 10px 0 0; } .content { background: #2a2a2a; padding: 30px; border-radius: 0 0 10px 10px; } .button { display: inline-block; background: #FFD700; color: #1a1a1a; padding: 15px 40px; text-decoration: none; border-radius: 5px; font-weight: bold; margin: 20px 0; } .footer { text-align: center; margin-top: 30px; color: #888; font-size: 12px; } </style> </head>🎸 GetYourBand.ch
Willkommen bei GetYourBand.ch!
Danke für deine Registrierung. Bitte bestätige deine E-Mail-Adresse, um deinen Account zu aktivieren:
Oder kopiere diesen Link in deinen Browser:
' . $verifyLink . '
Falls du dich nicht registriert hast, ignoriere diese E-Mail.
🎸 GetYourBand.ch
Passwort zurücksetzen
Du hast eine Anfrage zum Zurücksetzen deines Passworts gestellt. Klicke auf den Button, um ein neues Passwort zu vergeben:
Link gültig für 24 Stunden.
Falls du diese Anfrage nicht gestellt hast, ignoriere diese E-Mail.
🎸 GetYourBand.ch
Neue Buchungsanfrage!
' . sanitize($veranstalterName) . ' möchte euch für den ' . date('d.m.Y', strtotime($eventDate)) . ' buchen.
📁 Datei 7: login.php php
<?php require_once 'includes/config.php'; require_once 'includes/auth.php'; $auth = new Auth(); $error = ''; $success = ''; // Login-Verarbeitung if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) { $result = $auth->login($_POST['email'], $_POST['password']); if ($result['success']) { if ($result['user_type'] === 'band') { redirect('/profil.php'); } else { redirect('/index.php'); } } else { $error = $result['error']; } } // Registrierungs-Verarbeitung if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['register'])) { $result = $auth->register($_POST['email'], $_POST['password'], $_POST['user_type']); if ($result['success']) { $success = 'Registrierung erfolgreich! Bitte überprüfe deine E-Mails zur Verifizierung.'; } else { $error = $result['error']; } } ?> <html lang="de"> <head> </head>🎸 GetYourBand.ch
Die funky Bandvermittlung
<?php if ($error): ?>
<div class="alert alert-error"><?= sanitize($error) ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success"><?= sanitize($success) ?></div>
<?php endif; ?>
<div class="auth-tabs">
<button class="tab-btn active" onclick="showTab('login')">Login</button>
<button class="tab-btn" onclick="showTab('register')">Registrieren</button>
</div>
<!-- Login-Formular -->
<div id="login-tab" class="tab-content active">
<form method="POST" class="auth-form">
<div class="form-group">
<label for="login-email">E-Mail</label>
<input type="email" id="login-email" name="email" required>
</div>
<div class="form-group">
<label for="login-password">Passwort</label>
<input type="password" id="login-password" name="password" required>
</div>
<button type="submit" name="login" class="btn btn-primary">Einloggen</button>
<p class="form-footer">
<a href="reset-password.php">Passwort vergessen?</a>
</p>
</form>
</div>
<!-- Registrierungs-Formular -->
<div id="register-tab" class="tab-content">
<form method="POST" class="auth-form">
<div class="form-group">
<label for="reg-email">E-Mail</label>
<input type="email" id="reg-email" name="email" required>
</div>
<div class="form-group">
<label for="reg-password">Passwort (min. 8 Zeichen)</label>
<input type="password" id="reg-password" name="password" minlength="8" required>
</div>
<div class="form-group">
<label>Ich bin...</label>
<div class="radio-group">
<label class="radio-label">
<input type="radio" name="user_type" value="band" required>
<span>🎸 Band / Musiker</span>
</label>
<label class="radio-label">
<input type="radio" name="user_type" value="veranstalter" required>
<span>🎉 Veranstalter</span>
</label>
</div>
</div>
<button type="submit" name="register" class="btn btn-primary">Registrieren</button>
<p class="form-footer">
Mit der Registrierung akzeptierst du unsere <a href="agb.php">AGB</a> und <a href="datenschutz.php">Datenschutzerklärung</a>.
</p>
</form>
</div>
<div class="auth-footer">
<a href="index.php">← Zurück zur Startseite</a>
</div>
</div>
<script>
function showTab(tabName) {
// Alle Tabs ausblenden
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('active');
});
// Gewählten Tab anzeigen
document.getElementById(tabName + '-tab').classList.add('active');
event.target.classList.add('active');
}
</script>
</html>
📁 Datei 8: verify-email.php php
<?php require_once 'includes/config.php'; require_once 'includes/auth.php'; $auth = new Auth(); $message = ''; $success = false; if (isset($_GET['token'])) { $result = $auth->verifyEmail($_GET['token']); if ($result['success']) { $success = true; $message = 'E-Mail erfolgreich verifiziert! Du kannst dich jetzt einloggen.'; } else { $message = $result['error']; } } else { $message = 'Kein Verifizierungs-Token gefunden.'; } ?> <html lang="de"> <head> </head>🎸 GetYourBand.ch
<?php if ($success): ?>
<div class="alert alert-success">
<h2>✅ <?= $message ?></h2>
<a href="login.php" class="btn btn-primary">Zum Login</a>
</div>
<?php else: ?>
<div class="alert alert-error">
<h2>❌ <?= $message ?></h2>
<a href="login.php" class="btn btn-secondary">Zurück zum Login</a>
</div>
<?php endif; ?>
</div>
</div>
</html>
📁 Datei 9: assets/css/style.css (Basis-Styling) css
/* GetYourBand.ch - Funky Design mit Gelbtönen */
:root { --primary: #FFD700; --secondary: #FFA500; --dark: #1a1a1a; --darker: #0d0d0d; --light: #f5f5f5; --gray: #888; --success: #4CAF50; --error: #f44336; }
- { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, var(--darker) 0%, var(--dark) 100%); color: var(--light); min-height: 100vh; line-height: 1.6; }
/* Auth-Container */ .auth-container { max-width: 500px; margin: 50px auto; padding: 20px; }
.auth-header { text-align: center; margin-bottom: 30px; }
.auth-header h1 { font-size: 3em; background: linear-gradient(135deg, var(--primary), var(--secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin-bottom: 10px; }
.auth-header p { color: var(--gray); font-size: 1.1em; }
/* Tabs */ .auth-tabs { display: flex; gap: 10px; margin-bottom: 30px; }
.tab-btn { flex: 1; padding: 15px; background: var(--dark); border: 2px solid var(--gray); color: var(--light); font-size: 1em; cursor: pointer; border-radius: 10px; transition: all 0.3s; }
.tab-btn:hover { border-color: var(--primary); }
.tab-btn.active { background: linear-gradient(135deg, var(--primary), var(--secondary)); color: var(--dark); border-color: var(--primary); font-weight: bold; }
/* Tab Content */ .tab-content { display: none; background: var(--dark); padding: 30px; border-radius: 15px; box-shadow: 0 10px 40px rgba(255, 215, 0, 0.1); }
.tab-content.active { display: block; animation: fadeIn 0.3s; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
/* Forms */ .auth-form { display: flex; flex-direction: column; gap: 20px; }
.form-group { display: flex; flex-direction: column; gap: 8px; }
.form-group label { color: var(--primary); font-weight: 600; }
.form-group input { padding: 12px; background: var(--darker); border: 2px solid var(--gray); border-radius: 8px; color: var(--light); font-size: 1em; transition: border-color 0.3s; }
.form-group input:focus { outline: none; border-color: var(--primary); }
/* Radio Buttons */ .radio-group { display: flex; flex-direction: column; gap: 10px; }
.radio-label { display: flex; align-items: center; gap: 10px; padding: 15px; background: var(--darker); border: 2px solid var(--gray); border-radius: 8px; cursor: pointer; transition: all 0.3s; }
.radio-label:hover { border-color: var(--primary); }
.radio-label input[type="radio"] { width: 20px; height: 20px; accent-color: var(--primary); }
.radio-label input[type="radio"]:checked + span { color: var(--primary); font-weight: bold; }
/* Buttons */ .btn { padding: 15px 30px; border: none; border-radius: 8px; font-size: 1em; font-weight: bold; cursor: pointer; transition: all 0.3s; text-decoration: none; display: inline-block; text-align: center; }
.btn-primary { background: linear-gradient(135deg, var(--primary), var(--secondary)); color: var(--dark); }
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 5px 20px rgba(255, 215, 0, 0.4); }
.btn-secondary { background: var(--gray); color: var(--light); }
/* Alerts */ .alert { padding: 15px; border-radius: 8px; margin-bottom: 20px; }
.alert-success { background: rgba(76, 175, 80, 0.2); border: 2px solid var(--success); color: var(--success); }
.alert-error { background: rgba(244, 67, 54, 0.2); border: 2px solid var(--error); color: var(--error); }
/* Footer */ .form-footer { text-align: center; color: var(--gray); font-size: 0.9em; }
.form-footer a { color: var(--primary); text-decoration: none; }
.form-footer a:hover { text-decoration: underline; }
.auth-footer { text-align: center; margin-top: 30px; }
.auth-footer a { color: var(--primary); text-decoration: none; }
/* Verify Container */ .verify-container { display: flex; justify-content: center; align-items: center; min-height: 100vh; padding: 20px; }
.verify-box { background: var(--dark); padding: 50px; border-radius: 15px; text-align: center; max-width: 500px; }
.verify-box h1 { font-size: 3em; background: linear-gradient(135deg, var(--primary), var(--secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 30px; }
.verify-box h2 { margin-bottom: 20px; }
/* Responsive */ @media (max-width: 768px) { .auth-container { margin: 20px auto; }
.auth-header h1 {
font-size: 2em;
}
.tab-content {
padding: 20px;
}
}
✅ Was ist jetzt fertig?
✅ Komplettes Authentifizierungs-System
✅ Login & Registrierung (Band + Veranstalter)
✅ E-Mail-Verifizierung
✅ Passwort-Reset-Funktion
✅ Funky Design mit Gelbtönen
✅ Mobile-responsive
✅ Sichere Session-Verwaltung
🚀 Nächster Schritt? Sag Bescheid für Schritt 3: Frontend (Startseite + Band-Übersicht + Detailseite mit Kalender)! 🎸 Sources Images Videos News html-seminar.de Einfache PHP & Datenbank (MySQL) basierte Benutzerverwaltung (Login/Logout/Registrierung) - AJAX Kompatibel - PHP - HTML-Seminar.de - Forum für HTML, CSS und PHP
Ziel: eine einfache Datenbank basierte Benutzerverwaltung erstellen Verwendete Techniken: PHP, PDO Schwierigkeitsgrad: für Anfänger geeignet, Grundkenntnisse über die Funktionsweisen von PHP und SQL vorausgesetzt Anmerkungen: verwendet PDO, wegen dem… tutsplus.com Die 20 besten Veranstaltungs- und Buchungskalender mit PHP-Scripts... und 3 kostenlose Optionen | Envato Tuts+
Es ist mit PayPal und Razorpay integriert. Der Vacation Rentals Booking Calendar ist einen Belegungsplan mit Buchungsystem, mit dem Vermietungsbetrieb oder Verwaltungsagenturen Kalender für die Verfügbarkeit ihrer Ferienunterkünfte erstellen und verwalten können. prepaid-hoster.de Server mit Apache, PHP, MySQL mieten - Prepaid Hoster
Webserver-Ready in wenigen Sekunden · Einfaches Webserver Hosting auf dem eigenen vServer zoechbauer.name Projekt Webserver – Ubuntu 22.04 Teil 3 – PHP und MySQL nachinstallieren und konfigurieren
Im Teil 2 dieser Serie haben wir einen funktionierenden Webserver für statische HTML-Webseiten erstellt. Jetzt erweitern wir den Funktionsumfang noch etwas ind Nächster Schritt? Sag Bescheid für Schritt 3: Frontend (Startseite + Band-Übersicht + Detailseite mit Kalender)! 🎸 // weiter schritt 3 Workflow
Thinking
Searching
Thinking
Responding 🎸 SCHRITT 3: Frontend (Startseite + Band-Übersicht + Detailseite mit Kalender) Jetzt wird's funky! Hier kommt das komplette Frontend mit modernstem Design! 📁 Datei 10: index.php (Startseite mit Band-Übersicht) php
<?php require_once 'includes/config.php'; // Suchparameter $searchGenre = $_GET['genre'] ?? ''; $searchLocation = $_GET['location'] ?? ''; // Bands aus Datenbank laden (nur freigeschaltete) $db = getDB(); $sql = " SELECT b.*, u.email, (SELECT AVG(rating) FROM bewertungen WHERE band_id = b.id AND is_approved = 1) as avg_rating, (SELECT COUNT(*) FROM bewertungen WHERE band_id = b.id AND is_approved = 1) as rating_count FROM bands b JOIN users u ON b.user_id = u.id WHERE b.is_approved = 1 "; $params = []; if ($searchGenre) { $sql .= " AND b.genre LIKE ?"; $params[] = "%$searchGenre%"; } if ($searchLocation) { $sql .= " AND b.location LIKE ?"; $params[] = "%$searchLocation%"; } $sql .= " ORDER BY b.created_at DESC"; $stmt = $db->prepare($sql); $stmt->execute($params); $bands = $stmt->fetchAll(); // Verfügbare Genres für Filter $genresStmt = $db->query("SELECT DISTINCT genre FROM bands WHERE is_approved = 1 AND genre IS NOT NULL ORDER BY genre"); $genres = $genresStmt->fetchAll(PDO::FETCH_COLUMN); ?> <html lang="de"> <head> </head>🎸 GetYourBand.ch
Die funky Bandvermittlung
<!-- Hero Section -->
<section class="hero">
<div class="container">
<div class="hero-content">
<h2 class="hero-title">Finde deine perfekte Band</h2>
<p class="hero-subtitle">Über <?= count($bands) ?> professionelle Bands für deinen Event</p>
<!-- Suchfilter -->
<form method="GET" class="search-form">
<div class="search-inputs">
<div class="search-field">
<label>🎵 Genre</label>
<select name="genre">
<option value="">Alle Genres</option>
<?php foreach ($genres as $genre): ?>
<option value="<?= sanitize($genre) ?>" <?= $searchGenre === $genre ? 'selected' : '' ?>>
<?= sanitize($genre) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="search-field">
<label>📍 Ort</label>
<input type="text" name="location" placeholder="z.B. Zürich" value="<?= sanitize($searchLocation) ?>">
</div>
<button type="submit" class="btn btn-primary">Suchen</button>
</div>
</form>
</div>
</div>
</section>
<!-- Band-Übersicht -->
<section class="bands-section">
<div class="container">
<?php if (empty($bands)): ?>
<div class="no-results">
<h3>😕 Keine Bands gefunden</h3>
<p>Versuche es mit anderen Suchkriterien.</p>
<a href="index.php" class="btn btn-secondary">Filter zurücksetzen</a>
</div>
<?php else: ?>
<div class="bands-grid">
<?php foreach ($bands as $band): ?>
<div class="band-card">
<div class="band-image">
<?php
// Erstes Foto der Band laden
$mediaStmt = $db->prepare("SELECT file_path FROM band_media WHERE band_id = ? AND media_type = 'photo' ORDER BY display_order LIMIT 1");
$mediaStmt->execute([$band['id']]);
$photo = $mediaStmt->fetch();
?>
<?php if ($photo): ?>
<img src="<?= sanitize($photo['file_path']) ?>" alt="<?= sanitize($band['band_name']) ?>">
<?php else: ?>
<div class="placeholder-image">🎸</div>
<?php endif; ?>
<?php if ($band['genre']): ?>
<span class="genre-badge"><?= sanitize($band['genre']) ?></span>
<?php endif; ?>
</div>
<div class="band-info">
<h3 class="band-name"><?= sanitize($band['band_name']) ?></h3>
<?php if ($band['location']): ?>
<p class="band-location">📍 <?= sanitize($band['location']) ?></p>
<?php endif; ?>
<?php if ($band['avg_rating']): ?>
<div class="band-rating">
<span class="stars">
<?php
$rating = round($band['avg_rating']);
for ($i = 1; $i <= 5; $i++) {
echo $i <= $rating ? '⭐' : '☆';
}
?>
</span>
<span class="rating-count">(<?= $band['rating_count'] ?> Bewertungen)</span>
</div>
<?php endif; ?>
<?php if ($band['description']): ?>
<p class="band-description">
<?= sanitize(substr($band['description'], 0, 120)) ?>...
</p>
<?php endif; ?>
<?php if ($band['price_range']): ?>
<p class="band-price">💰 <?= sanitize($band['price_range']) ?></p>
<?php endif; ?>
<a href="band-detail.php?id=<?= $band['id'] ?>" class="btn btn-primary btn-block">
Details & Anfragen
</a>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</section>
<!-- Footer -->
<footer class="main-footer">
<div class="container">
<div class="footer-content">
<div class="footer-section">
<h4>GetYourBand.ch</h4>
<p>Die funky Bandvermittlung für unvergessliche Events</p>
</div>
<div class="footer-section">
<h4>Links</h4>
<ul>
<li><a href="agb.php">AGB</a></li>
<li><a href="datenschutz.php">Datenschutz</a></li>
<li><a href="impressum.php">Impressum</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Kontakt</h4>
<p>info@getyourband.ch</p>
</div>
</div>
<div class="footer-bottom">
<p>© 2025 GetYourBand.ch - Alle Rechte vorbehalten</p>
</div>
</div>
</footer>
<script src="assets/js/app.js"></script>
</html>
📁 Datei 11: band-detail.php (Detailseite mit Kalender) php
<?php require_once 'includes/config.php'; $bandId = $_GET['id'] ?? 0; if (!$bandId) { redirect('/index.php'); } $db = getDB(); // Band-Daten laden $stmt = $db->prepare(" SELECT b.*, u.email, (SELECT AVG(rating) FROM bewertungen WHERE band_id = b.id AND is_approved = 1) as avg_rating, (SELECT COUNT(*) FROM bewertungen WHERE band_id = b.id AND is_approved = 1) as rating_count FROM bands b JOIN users u ON b.user_id = u.id WHERE b.id = ? AND b.is_approved = 1 "); $stmt->execute([$bandId]); $band = $stmt->fetch(); if (!$band) { redirect('/index.php'); } // Medien laden $mediaStmt = $db->prepare("SELECT * FROM band_media WHERE band_id = ? ORDER BY display_order"); $mediaStmt->execute([$bandId]); $media = $mediaStmt->fetchAll(); // Bewertungen laden $reviewsStmt = $db->prepare(" SELECT r.*, u.email FROM bewertungen r JOIN users u ON r.veranstalter_id = u.id WHERE r.band_id = ? AND r.is_approved = 1 ORDER BY r.created_at DESC "); $reviewsStmt->execute([$bandId]); $reviews = $reviewsStmt->fetchAll(); // Verfügbarkeit für nächste 90 Tage laden $availabilityStmt = $db->prepare(" SELECT date, is_available FROM availability WHERE band_id = ? AND date >= DATE('now') AND date <= DATE('now', '+90 days') "); $availabilityStmt->execute([$bandId]); $availability = []; while ($row = $availabilityStmt->fetch()) { $availability[$row['date']] = $row['is_available']; } ?> <html lang="de"> <head> </head>🎸 GetYourBand.ch
<!-- Band-Detail -->
<section class="band-detail">
<div class="container">
<div class="detail-grid">
<!-- Linke Spalte: Medien & Info -->
<div class="detail-main">
<!-- Medien-Galerie -->
<div class="media-gallery">
<?php if (!empty($media)): ?>
<div class="main-media">
<?php
$firstMedia = $media[0];
if ($firstMedia['media_type'] === 'photo'):
?>
<img src="<?= sanitize($firstMedia['file_path']) ?>" alt="<?= sanitize($band['band_name']) ?>" id="main-image">
<?php else: ?>
<iframe src="<?= sanitize($firstMedia['video_url']) ?>" frameborder="0" allowfullscreen></iframe>
<?php endif; ?>
</div>
<?php if (count($media) > 1): ?>
<div class="media-thumbnails">
<?php foreach ($media as $item): ?>
<?php if ($item['media_type'] === 'photo'): ?>
<img src="<?= sanitize($item['file_path']) ?>"
alt="Thumbnail"
class="thumbnail"
onclick="changeMainImage('<?= sanitize($item['file_path']) ?>')">
<?php else: ?>
<div class="video-thumbnail" onclick="changeMainVideo('<?= sanitize($item['video_url']) ?>')">
▶️ Video
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php else: ?>
<div class="placeholder-image-large">🎸</div>
<?php endif; ?>
</div>
<!-- Band-Info -->
<div class="band-detail-info">
<h1><?= sanitize($band['band_name']) ?></h1>
<div class="info-badges">
<?php if ($band['genre']): ?>
<span class="badge badge-genre">🎵 <?= sanitize($band['genre']) ?></span>
<?php endif; ?>
<?php if ($band['location']): ?>
<span class="badge badge-location">📍 <?= sanitize($band['location']) ?></span>
<?php endif; ?>
<?php if ($band['price_range']): ?>
<span class="badge badge-price">💰 <?= sanitize($band['price_range']) ?></span>
<?php endif; ?>
</div>
<?php if ($band['avg_rating']): ?>
<div class="rating-display">
<span class="stars-large">
<?php
$rating = round($band['avg_rating']);
for ($i = 1; $i <= 5; $i++) {
echo $i <= $rating ? '⭐' : '☆';
}
?>
</span>
<span class="rating-text">
<?= number_format($band['avg_rating'], 1) ?> / 5.0
(<?= $band['rating_count'] ?> Bewertungen)
</span>
</div>
<?php endif; ?>
<?php if ($band['description']): ?>
<div class="description">
<h3>Über uns</h3>
<p><?= nl2br(sanitize($band['description'])) ?></p>
</div>
<?php endif; ?>
<?php if ($band['website']): ?>
<div class="website">
<a href="<?= sanitize($band['website']) ?>" target="_blank" class="btn btn-secondary">
🌐 Website besuchen
</a>
</div>
<?php endif; ?>
</div>
<!-- Bewertungen -->
<?php if (!empty($reviews)): ?>
<div class="reviews-section">
<h3>Bewertungen</h3>
<div class="reviews-list">
<?php foreach ($reviews as $review): ?>
<div class="review-card">
<div class="review-header">
<span class="review-stars">
<?php
for ($i = 1; $i <= 5; $i++) {
echo $i <= $review['rating'] ? '⭐' : '☆';
}
?>
</span>
<span class="review-date">
<?= date('d.m.Y', strtotime($review['created_at'])) ?>
</span>
</div>
<p class="review-comment"><?= sanitize($review['comment']) ?></p>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<!-- Rechte Spalte: Kalender & Anfrage -->
<div class="detail-sidebar">
<!-- Verfügbarkeitskalender -->
<div class="availability-widget">
<h3>Verfügbarkeit</h3>
<div id="calendar"></div>
<div class="calendar-legend">
<span><span class="legend-available"></span> Verfügbar</span>
<span><span class="legend-booked"></span> Gebucht</span>
</div>
</div>
<!-- Anfrage-Formular -->
<div class="inquiry-widget">
<h3>Anfrage senden</h3>
<?php if (isLoggedIn()): ?>
<form method="POST" action="anfrage.php" class="inquiry-form">
<input type="hidden" name="band_id" value="<?= $band['id'] ?>">
<div class="form-group">
<label>Dein Name</label>
<input type="text" name="name" required>
</div>
<div class="form-group">
<label>E-Mail</label>
<input type="email" name="email" value="<?= sanitize($_SESSION['email']) ?>" required>
</div>
<div class="form-group">
<label>Telefon</label>
<input type="tel" name="phone">
</div>
<div class="form-group">
<label>Event-Datum</label>
<input type="date" name="event_date" required>
</div>
<div class="form-group">
<label>Event-Ort</label>
<input type="text" name="event_location" required>
</div>
<div class="form-group">
<label>Event-Typ</label>
<select name="event_type">
<option value="Hochzeit">Hochzeit</option>
<option value="Geburtstag">Geburtstag</option>
<option value="Firmenfeier">Firmenfeier</option>
<option value="Festival">Festival</option>
<option value="Sonstiges">Sonstiges</option>
</select>
</div>
<div class="form-group">
<label>Budget</label>
<input type="text" name="budget" placeholder="z.B. CHF 2000-3000">
</div>
<div class="form-group">
<label>Nachricht</label>
<textarea name="message" rows="4" placeholder="Weitere Details zu deinem Event..."></textarea>
</div>
<button type="submit" class="btn btn-primary btn-block">
Anfrage senden
</button>
</form>
<?php else: ?>
<p>Bitte <a href="login.php">logge dich ein</a>, um eine Anfrage zu senden.</p>
<?php endif; ?>
</div>
</div>
</div>
</div>
</section>
<footer class="main-footer">
<div class="container">
<p>© 2025 GetYourBand.ch</p>
</div>
</footer>
<script>
// Kalender-Daten aus PHP
const availability = <?= json_encode($availability) ?>;
// Medien-Galerie
function changeMainImage(src) {
document.getElementById('main-image').src = src;
}
function changeMainVideo(url) {
const mainMedia = document.querySelector('.main-media');
mainMedia.innerHTML = '<iframe src="' + url + '" frameborder="0" allowfullscreen></iframe>';
}
</script>
<script src="assets/js/calendar.js"></script>
</html>
📁 Datei 12: assets/css/bands.css css
/* Band-Übersicht & Detail-Styling */
/* Hero Section */ .hero { background: linear-gradient(135deg, var(--darker) 0%, var(--dark) 50%, var(--darker) 100%); padding: 80px 20px; text-align: center; position: relative; overflow: hidden; }
.hero::before { content: '🎸'; position: absolute; font-size: 300px; opacity: 0.05; top: 50%; left: 50%; transform: translate(-50%, -50%) rotate(-15deg); }
.hero-content { position: relative; z-index: 1; }
.hero-title { font-size: 3.5em; background: linear-gradient(135deg, var(--primary), var(--secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 20px; }
.hero-subtitle { font-size: 1.3em; color: var(--gray); margin-bottom: 40px; }
/* Suchformular */ .search-form { max-width: 900px; margin: 0 auto; }
.search-inputs { display: grid; grid-template-columns: 1fr 1fr auto; gap: 15px; background: var(--dark); padding: 20px; border-radius: 15px; box-shadow: 0 10px 40px rgba(255, 215, 0, 0.1); }
.search-field { display: flex; flex-direction: column; gap: 8px; }
.search-field label { color: var(--primary); font-weight: 600; font-size: 0.9em; }
.search-field input, .search-field select { padding: 12px; background: var(--darker); border: 2px solid var(--gray); border-radius: 8px; color: var(--light); font-size: 1em; }
.search-field input:focus, .search-field select:focus { outline: none; border-color: var(--primary); }
/* Band-Grid */ .bands-section { padding: 60px 20px; }
.bands-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 30px; }
.band-card { background: var(--dark); border-radius: 15px; overflow: hidden; transition: transform 0.3s, box-shadow 0.3s; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3); }
.band-card:hover { transform: translateY(-5px); box-shadow: 0 10px 40px rgba(255, 215, 0, 0.2); }
.band-image { position: relative; height: 250px; overflow: hidden; background: var(--darker); }
.band-image img { width: 100%; height: 100%; object-fit: cover; }
.placeholder-image { display: flex; align-items: center; justify-content: center; height: 100%; font-size: 80px; background: linear-gradient(135deg, var(--darker), var(--dark)); }
.genre-badge { position: absolute; top: 15px; right: 15px; background: var(--primary); color: var(--dark); padding: 8px 15px; border-radius: 20px; font-weight: bold; font-size: 0.85em; }
.band-info { padding: 25px; }
.band-name { font-size: 1.5em; margin-bottom: 10px; color: var(--primary); }
.band-location { color: var(--gray); margin-bottom: 10px; }
.band-rating { display: flex; align-items: center; gap: 10px; margin-bottom: 15px; }
.stars { font-size: 1.2em; }
.rating-count { color: var(--gray); font-size: 0.9em; }
.band-description { color: var(--light); margin-bottom: 15px; line-height: 1.6; }
.band-price { color: var(--primary); font-weight: bold; margin-bottom: 20px; }
.btn-block { width: 100%; text-align: center; }
/* Band-Detail */ .band-detail { padding: 40px 20px; }
.detail-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 40px; }
.detail-main { display: flex; flex-direction: column; gap: 30px; }
/* Medien-Galerie */ .media-gallery { background: var(--dark); border-radius: 15px; overflow: hidden; padding: 20px; }
.main-media { width: 100%; height: 500px; background: var(--darker); border-radius: 10px; overflow: hidden; margin-bottom: 15px; }
.main-media img { width: 100%; height: 100%; object-fit: cover; }
.main-media iframe { width: 100%; height: 100%; }
.media-thumbnails { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 10px; }
.thumbnail { width: 100%; height: 80px; object-fit: cover; border-radius: 8px; cursor: pointer; border: 2px solid transparent; transition: border-color 0.3s; }
.thumbnail:hover { border-color: var(--primary); }
.video-thumbnail { height: 80px; background: var(--darker); border-radius: 8px; display: flex; align-items: center; justify-content: center; cursor: pointer; border: 2px solid transparent; transition: all 0.3s; }
.video-thumbnail:hover { border-color: var(--primary); background: var(--dark); }
/* Band-Detail-Info */ .band-detail-info { background: var(--dark); padding: 30px; border-radius: 15px; }
.band-detail-info h1 { font-size: 2.5em; color: var(--primary); margin-bottom: 20px; }
.info-badges { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 20px; }
.badge { padding: 10px 20px; border-radius: 20px; font-weight: bold; font-size: 0.9em; }
.badge-genre { background: linear-gradient(135deg, var(--primary), var(--secondary)); color: var(--dark); }
.badge-location { background: var(--darker); color: var(--light); border: 2px solid var(--gray); }
.badge-price { background: var(--darker); color: var(--primary); border: 2px solid var(--primary); }
.rating-display { display: flex; align-items: center; gap: 15px; margin-bottom: 30px; padding: 20px; background: var(--darker); border-radius: 10px; }
.stars-large { font-size: 1.8em; }
.rating-text { color: var(--light); font-size: 1.1em; }
.description { margin-bottom: 30px; }
.description h3 { color: var(--primary); margin-bottom: 15px; }
.description p { line-height: 1.8; color:
Sources Images Videos News edorex.ch Was heisst «Frontend First»? - Edorex
Allenfalls kann aber vor diesem Schritt auch einmal eine Idee verworfen werden, ohne dass dies zu fest schmerzt («fail early, fail often»). Die Frontend First Philosophie reiht sich daher sehr gut in die agile Denkweise ein. door3.com Bewährte Praktiken und Trends in der Front-End-Entwicklung (Teil I)
Beherrschen Sie die besten Praktiken der Front-End-Entwicklung: UI/UX, Übergabe zwischen Design und Entwicklung, CSS-Frameworks, JS-Frameworks und mobile Int... mindtwo.de Frontend-Entwicklung: Alles Wichtige für Einsteiger und Profis
Zögern Sie nicht, den nächsten Schritt zu machen. Lassen Sie uns gemeinsam Ihre digitalen Ziele erreichen. Besuchen Sie unsere Projektanfrage Seite und teilen Sie uns Ihre Vision mit. codehan.de Webentwicklung, Frontend und Tech-Trends 2025: Das solltest du wissen - Codehan | Webentwicklung, Print & Mehr
2025 verspricht ein spannendes Jahr für Webentwickler und Frontend-Experten zu werden. Mit neuen Technologien, disruptiven Trends und sich verändernden Märkten steht die Branche vor einem weiteren Transformationsjahr. weiter Workflow
Thinking
Searching
Thinking
Responding 🎸 SCHRITT 3: Frontend (Fortsetzung) Hier ist die Fortsetzung von bands.css und die restlichen Frontend-Dateien: 📁 Datei 12: assets/css/bands.css (Fortsetzung) css
.description p { line-height: 1.8; color: var(--light); }
/* Bewertungen */ .reviews-section { background: var(--dark); padding: 30px; border-radius: 15px; }
.reviews-section h3 { color: var(--primary); margin-bottom: 20px; }
.reviews-list { display: flex; flex-direction: column; gap: 20px; }
.review-card { background: var(--darker); padding: 20px; border-radius: 10px; border-left: 4px solid var(--primary); }
.review-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.review-stars { font-size: 1.2em; }
.review-date { color: var(--gray); font-size: 0.9em; }
.review-comment { color: var(--light); line-height: 1.6; }
/* Sidebar */ .detail-sidebar { display: flex; flex-direction: column; gap: 30px; }
.availability-widget, .inquiry-widget { background: var(--dark); padding: 25px; border-radius: 15px; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3); }
.availability-widget h3, .inquiry-widget h3 { color: var(--primary); margin-bottom: 20px; font-size: 1.3em; }
.calendar-legend { display: flex; gap: 20px; margin-top: 15px; font-size: 0.9em; }
.calendar-legend span { display: flex; align-items: center; gap: 8px; }
.legend-available, .legend-booked { width: 20px; height: 20px; border-radius: 4px; }
.legend-available { background: var(--success); }
.legend-booked { background: var(--error); }
/* Anfrage-Formular */ .inquiry-form { display: flex; flex-direction: column; gap: 15px; }
.inquiry-form .form-group { display: flex; flex-direction: column; gap: 8px; }
.inquiry-form label { color: var(--primary); font-weight: 600; font-size: 0.9em; }
.inquiry-form input, .inquiry-form select, .inquiry-form textarea { padding: 12px; background: var(--darker); border: 2px solid var(--gray); border-radius: 8px; color: var(--light); font-size: 1em; font-family: inherit; }
.inquiry-form input:focus, .inquiry-form select:focus, .inquiry-form textarea:focus { outline: none; border-color: var(--primary); }
.inquiry-form textarea { resize: vertical; }
/* No Results */ .no-results { text-align: center; padding: 80px 20px; }
.no-results h3 { font-size: 2em; margin-bottom: 20px; }
.no-results p { color: var(--gray); margin-bottom: 30px; }
/* Responsive */ @media (max-width: 1024px) { .detail-grid { grid-template-columns: 1fr; }
.search-inputs {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) { .hero-title { font-size: 2em; }
.bands-grid {
grid-template-columns: 1fr;
}
.main-media {
height: 300px;
}
.band-detail-info h1 {
font-size: 1.8em;
}
}
📁 Datei 13: assets/css/calendar.css css
/* Kalender-Styling */
#calendar { background: var(--darker); border-radius: 10px; padding: 15px; }
.calendar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.calendar-header h4 { color: var(--primary); font-size: 1.2em; }
.calendar-nav { display: flex; gap: 10px; }
.calendar-nav button { background: var(--dark); border: 2px solid var(--gray); color: var(--light); padding: 8px 15px; border-radius: 5px; cursor: pointer; transition: all 0.3s; }
.calendar-nav button:hover { border-color: var(--primary); color: var(--primary); }
.calendar-weekdays { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; margin-bottom: 10px; }
.calendar-weekday { text-align: center; color: var(--gray); font-size: 0.85em; font-weight: bold; padding: 8px 0; }
.calendar-days { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; }
.calendar-day { aspect-ratio: 1; display: flex; align-items: center; justify-content: center; border-radius: 8px; font-size: 0.9em; cursor: pointer; transition: all 0.3s; position: relative; }
.calendar-day.empty { cursor: default; }
.calendar-day.today { border: 2px solid var(--primary); font-weight: bold; }
.calendar-day.available { background: rgba(76, 175, 80, 0.2); color: var(--success); }
.calendar-day.available:hover { background: rgba(76, 175, 80, 0.4); }
.calendar-day.booked { background: rgba(244, 67, 54, 0.2); color: var(--error); cursor: not-allowed; }
.calendar-day.past { color: var(--gray); opacity: 0.5; cursor: not-allowed; }
.calendar-day.selected { background: var(--primary); color: var(--dark); font-weight: bold; }
/* Tooltip */ .calendar-day[data-tooltip]:hover::after { content: attr(data-tooltip); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); background: var(--dark); color: var(--light); padding: 5px 10px; border-radius: 5px; font-size: 0.8em; white-space: nowrap; z-index: 10; margin-bottom: 5px; }
📁 Datei 14: assets/js/calendar.js javascript
// Kalender-Funktionalität für GetYourBand.ch
class BandCalendar { constructor(containerId, availabilityData) { this.container = document.getElementById(containerId); this.availability = availabilityData; this.currentDate = new Date(); this.selectedDate = null;
this.init();
}
init() {
this.render();
}
render() {
const year = this.currentDate.getFullYear();
const month = this.currentDate.getMonth();
const monthNames = [
'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'
];
const weekdays = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
// Header
let html = `
<div class="calendar-header">
<h4>${monthNames[month]} ${year}</h4>
<div class="calendar-nav">
<button onclick="calendar.previousMonth()">←</button>
<button onclick="calendar.nextMonth()">→</button>
</div>
</div>
`;
// Wochentage
html += '<div class="calendar-weekdays">';
weekdays.forEach(day => {
html += `<div class="calendar-weekday">${day}</div>`;
});
html += '</div>';
// Tage
html += '<div class="calendar-days">';
// Erster Tag des Monats
const firstDay = new Date(year, month, 1);
let dayOfWeek = firstDay.getDay();
dayOfWeek = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Montag = 0
// Leere Zellen vor dem ersten Tag
for (let i = 0; i < dayOfWeek; i++) {
html += '<div class="calendar-day empty"></div>';
}
// Tage des Monats
const daysInMonth = new Date(year, month + 1, 0).getDate();
const today = new Date();
today.setHours(0, 0, 0, 0);
for (let day = 1; day <= daysInMonth; day++) {
const date = new Date(year, month, day);
const dateStr = this.formatDate(date);
let classes = ['calendar-day'];
let tooltip = '';
// Heute markieren
if (date.getTime() === today.getTime()) {
classes.push('today');
}
// Vergangene Tage
if (date < today) {
classes.push('past');
}
// Verfügbarkeit prüfen
if (this.availability[dateStr] !== undefined) {
if (this.availability[dateStr] == 1) {
classes.push('available');
tooltip = 'Verfügbar';
} else {
classes.push('booked');
tooltip = 'Gebucht';
}
}
// Ausgewähltes Datum
if (this.selectedDate && this.selectedDate === dateStr) {
classes.push('selected');
}
html += `
<div class="${classes.join(' ')}"
data-date="${dateStr}"
data-tooltip="${tooltip}"
onclick="calendar.selectDate('${dateStr}')">
${day}
</div>
`;
}
html += '</div>';
this.container.innerHTML = html;
}
formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
selectDate(dateStr) {
const date = new Date(dateStr);
const today = new Date();
today.setHours(0, 0, 0, 0);
// Vergangene Tage nicht auswählbar
if (date < today) {
return;
}
// Gebuchte Tage nicht auswählbar
if (this.availability[dateStr] === 0) {
return;
}
this.selectedDate = dateStr;
this.render();
// Datum ins Formular eintragen
const dateInput = document.querySelector('input[name="event_date"]');
if (dateInput) {
dateInput.value = dateStr;
}
}
previousMonth() {
this.currentDate.setMonth(this.currentDate.getMonth() - 1);
this.render();
}
nextMonth() {
this.currentDate.setMonth(this.currentDate.getMonth() + 1);
this.render();
}
}
// Kalender initialisieren (wird von band-detail.php aufgerufen) let calendar; document.addEventListener('DOMContentLoaded', function() { if (typeof availability !== 'undefined') { calendar = new BandCalendar('calendar', availability); } });
📁 Datei 15: assets/js/app.js javascript
// Haupt-JavaScript für GetYourBand.ch
document.addEventListener('DOMContentLoaded', function() {
// Smooth Scroll für Anker-Links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
// Lazy Loading für Bilder
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
});
});
images.forEach(img => imageObserver.observe(img));
// Mobile Navigation Toggle
const navToggle = document.querySelector('.nav-toggle');
const mainNav = document.querySelector('.main-nav');
if (navToggle) {
navToggle.addEventListener('click', function() {
mainNav.classList.toggle('active');
});
}
// Form Validation
const forms = document.querySelectorAll('form[data-validate]');
forms.forEach(form => {
form.addEventListener('submit', function(e) {
if (!validateForm(this)) {
e.preventDefault();
}
});
});
// Auto-hide Alerts nach 5 Sekunden
const alerts = document.querySelectorAll('.alert');
alerts.forEach(alert => {
setTimeout(() => {
alert.style.opacity = '0';
setTimeout(() => alert.remove(), 300);
}, 5000);
});
});
// Form Validation Funktion function validateForm(form) { let isValid = true; const inputs = form.querySelectorAll('input[required], textarea[required], select[required]');
inputs.forEach(input => {
if (!input.value.trim()) {
showError(input, 'Dieses Feld ist erforderlich');
isValid = false;
} else {
clearError(input);
}
// E-Mail Validierung
if (input.type === 'email' && input.value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(input.value)) {
showError(input, 'Ungültige E-Mail-Adresse');
isValid = false;
}
}
});
return isValid;
}
function showError(input, message) { clearError(input); input.classList.add('error'); const errorDiv = document.createElement('div'); errorDiv.className = 'error-message'; errorDiv.textContent = message; input.parentNode.appendChild(errorDiv); }
function clearError(input) { input.classList.remove('error'); const errorMsg = input.parentNode.querySelector('.error-message'); if (errorMsg) { errorMsg.remove(); } }
// Utility: Format Datum
function formatDate(dateString) {
const date = new Date(dateString);
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
return ${day}.${month}.${year};
}
// Utility: Debounce für Search function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }
// Live Search (optional) const searchInput = document.querySelector('input[name="search"]'); if (searchInput) { searchInput.addEventListener('input', debounce(function(e) { // Hier könnte AJAX-Suche implementiert werden console.log('Suche nach:', e.target.value); }, 500)); }
📁 Datei 16: logout.php php
<?php require_once 'includes/config.php'; require_once 'includes/auth.php'; $auth = new Auth(); $auth->logout(); redirect('/index.php'); ?>📁 Datei 17: assets/css/style.css (Ergänzungen) css
/* Zusätzliche globale Styles */
/* Container */ .container { max-width: 1400px; margin: 0 auto; padding: 0 20px; }
/* Header */ .main-header { background: var(--dark); border-bottom: 2px solid var(--primary); padding: 20px 0; position: sticky; top: 0; z-index: 100; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3); }
.header-content { display: flex; justify-content: space-between; align-items: center; }
.logo h1 { font-size: 1.8em; background: linear-gradient(135deg, var(--primary), var(--secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin: 0; }
.logo p { color: var(--gray); font-size: 0.85em; margin: 0; }
.main-nav { display: flex; gap: 20px; align-items: center; }
.nav-link { color: var(--light); text-decoration: none; padding: 10px 20px; border-radius: 5px; transition: all 0.3s; }
.nav-link:hover { color: var(--primary); background: var(--darker); }
.nav-link.active { color: var(--primary); font-weight: bold; }
/* Footer */ .main-footer { background: var(--darker); border-top: 2px solid var(--primary); padding: 40px 0 20px; margin-top: 80px; }
.footer-content { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 40px; margin-bottom: 30px; }
.footer-section h4 { color: var(--primary); margin-bottom: 15px; }
.footer-section p, .footer-section ul { color: var(--gray); line-height: 1.8; }
.footer-section ul { list-style: none; padding: 0; }
.footer-section ul li { margin-bottom: 8px; }
.footer-section a { color: var(--gray); text-decoration: none; transition: color 0.3s; }
.footer-section a:hover { color: var(--primary); }
.footer-bottom { text-align: center; padding-top: 20px; border-top: 1px solid var(--gray); color: var(--gray); }
/* Error Messages */ .error-message { color: var(--error); font-size: 0.85em; margin-top: 5px; }
input.error, textarea.error, select.error { border-color: var(--error) !important; }
/* Loading Spinner */ .loading { display: inline-block; width: 20px; height: 20px; border: 3px solid var(--gray); border-top-color: var(--primary); border-radius: 50%; animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
/* Responsive Navigation */ @media (max-width: 768px) { .header-content { flex-direction: column; gap: 20px; }
.main-nav {
flex-direction: column;
width: 100%;
}
.nav-link {
width: 100%;
text-align: center;
}
.footer-content {
grid-template-columns: 1fr;
text-align: center;
}
}
/* Animations */ @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
.fade-in { animation: fadeIn 0.5s ease-out; }
/* Utility Classes */ .text-center { text-align: center; } .text-right { text-align: right; } .mt-20 { margin-top: 20px; } .mb-20 { margin-bottom: 20px; } .hidden { display: none; }
✅ Was ist jetzt fertig?
✅ Komplette Startseite mit Band-Übersicht
✅ Suchfilter (Genre, Ort)
✅ Band-Detailseite mit Medien-Galerie
✅ Interaktiver Verfügbarkeitskalender
✅ Anfrage-Formular
✅ Bewertungsanzeige
✅ Funky Design mit Gelbtönen
✅ Vollständig responsive
✅ Smooth Animations
✅ Mobile-optimiert
🚀 Nächster Schritt? Sag Bescheid für Schritt 4: Anfrage-System & Band-Profil-Verwaltung! 🎸✨ Sources Images Videos News df.eu Eine eigene Webseite erstellen – Schritt für Schritt - Domain Factory Blog
Mit unserer Schritt für Schritt Anleitung kannst du ganz einfach eine professionelle Webseite erstellen. Erfahre hier, was du dafür brauchst. androidmag.de Kalender: Termine auf der Startseite anzeigen - Androidmag
Widgets auf der Startseite machen den Blick in den Terminkalender unnötig. Flexibler als das Standard-Widget ist das Clean Calendar Widget. agentur-ibk.de Webseite in TYPO3 einrichten: Seite im Backend konfigurieren
Im nächsten Schritt gehen Sie nun auf die Seiteneigenschaften und dort auf den Reiter „Verhalten“. Sie sehen dort in der Kategorie „Sonstige“ den Schalter „Als Anfang der Webseite benutzen“. adva.de Schritt-für-Schritt-Anleitung: So erstellen Sie eine TYPO3-Website | ADVA Communication
Mit TYPO3 erstellen Sie Schritt für Schritt eine professionelle Website – einfach, effizient und perfekt auf Ihre Anforderungen zugeschnitten.