Files
2025-11-17 14:53:12 +01:00

4430 lines
133 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
1. Datenbankstruktur & Hosting
Welches DBMS? (MySQL, PostgreSQL, SQLite?)
Hosting-Umgebung vorhanden? (PHP-Version?)
Soll ich SQL-Schema mitliefern?
2. Benutzerrollen & Authentifizierung
Wer kann sich registrieren? (Nur Bands / Auch Veranstalter/Kunden?)
Login-System erforderlich?
Registrierungsprozess mit E-Mail-Verifizierung?
3. Band-Profile
Welche Pflichtfelder? (Name, Genre, Ort, Preis, Fotos, Videos?)
Mehrere Fotos/Videos pro Band?
Beschreibungstexte mit Formatierung?
Verfügbarkeitskalender integrieren?
4. Bewertungssystem
Wer darf bewerten? (Nur nach Buchung / Öffentlich?)
Sterne-System (1-5) oder andere Skala?
Textbewertungen erlaubt?
Moderation von Bewertungen?
5. Anfragen/Buchungssystem
Direktbuchung oder Anfrage-Formular?
Welche Daten beim Anfragen? (Datum, Ort, Budget, Event-Typ?)
E-Mail-Benachrichtigungen an Band?
Anfragen-Verwaltung im Backend?
6. Suchfunktionen & Filter
Suche nach: Genre, PLZ/Ort, Preis, Verfügbarkeit?
Kartenansicht (Google Maps API)?
Sortierung (Bewertung, Preis, Entfernung)?
7. Design & Frontend
Welcher "funky" Stil? (Neon, Retro, Modern-Minimalistisch?)
Farbschema-Vorstellungen?
Mobile-First wichtig?
CSS-Framework gewünscht? (Tailwind, Bootstrap, Custom?)
8. 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?)
9. Zahlungssystem
Vermittlungsgebühr?
Zahlungsabwicklung integrieren? (Stripe, PayPal?)
Oder nur Kontaktvermittlung ohne Zahlung?
10. Admin/Moderation
Admin-Bereich für Plattform-Betreiber?
Bands können eigene Profile bearbeiten?
Freischaltung neuer Bands erforderlich?
11. 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! 🎸
1. 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 </Directory> <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
</FilesMatch>
# 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 "<h1>🎸 GetYourBand.ch Setup</h1>";
// Datenbank-Ordner erstellen
$dbDir = BASE_PATH . '/database';
if (!file_exists($dbDir)) {
mkdir($dbDir, 0755, true);
echo "✅ Datenbank-Ordner erstellt<br>";
}
// 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<br>";
}
}
// 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<br>";
// 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!<br>";
echo "<br><strong⚠️ WICHTIG: Lösche setup.php nach erfolgreicher Installation!</strong>";
} 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 doesnt 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("
<h2>Willkommen bei GetYourBand.ch!</h2>
<p>Vielen Dank für deine Registrierung. Bitte bestätige deine E-Mail-Adresse, um deinen Account zu aktivieren:</p>
<p style='text-align: center; margin: 30px 0;'>
<a href='$verificationLink' style='background: #FFD700; color: #000; padding: 15px 30px; text-decoration: none; border-radius: 5px; font-weight: bold;'>
E-Mail bestätigen
</a>
</p>
<p style='color: #666; font-size: 12px;'>
Falls der Button nicht funktioniert, kopiere diesen Link in deinen Browser:<br>
<a href='$verificationLink'>$verificationLink</a>
</p>
");
return sendEmail($email, $subject, $body);
}
/**
* Passwort-Reset-E-Mail senden
*/
function sendPasswordResetEmail($email, $resetLink) {
$subject = "🔐 Passwort zurücksetzen - GetYourBand.ch";
$body = getEmailTemplate("
<h2>Passwort zurücksetzen</h2>
<p>Du hast eine Anfrage zum Zurücksetzen deines Passworts gestellt. Klicke auf den Button, um ein neues Passwort zu vergeben:</p>
<p style='text-align: center; margin: 30px 0;'>
<a href='$resetLink' style='background: #FFD700; color: #000; padding: 15px 30px; text-decoration: none; border-radius: 5px; font-weight: bold;'>
Neues Passwort setzen
</a>
</p>
<p style='color: #666; font-size: 12px;'>
Falls du diese Anfrage nicht gestellt hast, ignoriere diese E-Mail einfach.<br><br>
Link: <a href='$resetLink'>$resetLink</a>
</p>
");
return sendEmail($email, $subject, $body);
}
/**
* Anfrage-Benachrichtigung an Band
*/
function sendAnfrageNotification($bandEmail, $bandName, $veranstalterName, $eventDate, $anfrageId) {
$subject = "🎉 Neue Buchungsanfrage für $bandName";
$anfrageLink = BASE_URL . "/admin/anfragen.php?id=$anfrageId";
$body = getEmailTemplate("
<h2>Neue Buchungsanfrage!</h2>
<p><strong>$veranstalterName</strong> möchte eure Band für einen Event buchen:</p>
<ul style='line-height: 1.8;'>
<li><strong>Datum:</strong> $eventDate</li>
</ul>
<p style='text-align: center; margin: 30px 0;'>
<a href='$anfrageLink' style='background: #FFD700; color: #000; padding: 15px 30px; text-decoration: none; border-radius: 5px; font-weight: bold;'>
Anfrage ansehen
</a>
</p>
");
return sendEmail($bandEmail, $subject, $body);
}
/**
* E-Mail-Template (Funky Design)
*/
function getEmailTemplate($content) {
return "
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<style>
body { font-family: 'Arial', sans-serif; background: #1a1a1a; margin: 0; padding: 0; }
.container { max-width: 600px; margin: 0 auto; background: #fff; }
.header { background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); padding: 30px; text-align: center; }
.header h1 { margin: 0; color: #000; font-size: 28px; }
.content { padding: 40px 30px; color: #333; line-height: 1.6; }
.footer { background: #f5f5f5; padding: 20px; text-align: center; color: #666; font-size: 12px; }
a { color: #FFA500; }
</style>
</head>
<body>
<div class='container'>
<div class='header'>
<h1>🎸 GetYourBand.ch</h1>
</div>
<div class='content'>
$content
</div>
<div class='footer'>
<p>&copy; 2025 GetYourBand.ch | <a href='" . BASE_URL . "'>Zur Website</a></p>
</div>
</div>
</body>
</html>
";
}
?>
📁 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.';
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $mode === 'register' ? 'Registrierung' : 'Login' ?> - GetYourBand.ch</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body class="auth-page">
<div class="auth-container">
<div class="auth-header">
<h1>🎸 GetYourBand.ch</h1>
<p>Die funky Bandvermittlung</p>
</div>
<?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>
</body>
</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.';
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>E-Mail Verifizierung - GetYourBand.ch</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body class="auth-page">
<div class="auth-container">
<div class="auth-header">
<h1>🎸 GetYourBand.ch</h1>
</div>
<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>
</body>
</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'];
}
}
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Passwort zurücksetzen - GetYourBand.ch</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body class="auth-page">
<div class="auth-container">
<div class="auth-header">
<h1>🎸 GetYourBand.ch</h1>
<h2>Neues Passwort setzen</h2>
</div>
<?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>
</body>
</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 = "
<!DOCTYPE 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: #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>
<body>
<div class='container'>
<div class='header'>
<h1 style='margin:0; color: #000;'>🎸 GetYourBand.ch</h1>
</div>
<div class='content'>
<h2>Willkommen bei GetYourBand.ch!</h2>
<p>Danke für deine Registrierung. Bitte bestätige deine E-Mail-Adresse, um deinen Account zu aktivieren:</p>
<p style='text-align: center;'>
<a href='$verificationLink' class='button'>E-Mail bestätigen</a>
</p>
<p style='color: #888; font-size: 12px;'>Oder kopiere diesen Link in deinen Browser:<br>
$verificationLink</p>
<p>Der Link ist 24 Stunden gültig.</p>
</div>
<div class='footer'>
© 2025 GetYourBand.ch - Die funky Bandvermittlung
</div>
</div>
</body>
</html>
";
return sendEmail($email, $subject, $body);
}
/**
* Passwort-Reset-E-Mail
*/
function sendPasswordResetEmail($email, $resetLink) {
$subject = "🔐 Passwort zurücksetzen - GetYourBand.ch";
$body = "
<!DOCTYPE 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: #000; padding: 15px 40px; text-decoration: none; border-radius: 5px; font-weight: bold; margin: 20px 0; }
</style>
</head>
<body>
<div class='container'>
<div class='header'>
<h1 style='margin:0; color: #000;'>🎸 GetYourBand.ch</h1>
</div>
<div class='content'>
<h2>Passwort zurücksetzen</h2>
<p>Du hast eine Passwort-Zurücksetzung angefordert. Klicke auf den Button, um ein neues Passwort zu setzen:</p>
<p style='text-align: center;'>
<a href='$resetLink' class='button'>Neues Passwort setzen</a>
</p>
<p style='color: #888; font-size: 12px;'>Falls du diese Anfrage nicht gestellt hast, ignoriere diese E-Mail einfach.</p>
</div>
</div>
</body>
</html>
";
return sendEmail($email, $subject, $body);
}
/**
* Benachrichtigung an Band bei neuer Anfrage
*/
function sendAnfrageNotification($bandEmail, $bandName, $veranstalterName, $eventDate) {
$subject = "🎉 Neue Buchungsanfrage für $bandName";
$body = "
<!DOCTYPE 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: #000; padding: 15px 40px; text-decoration: none; border-radius: 5px; font-weight: bold; margin: 20px 0; }
</style>
</head>
<body>
<div class='container'>
<div class='header'>
<h1 style='margin:0; color: #000;'>🎸 GetYourBand.ch</h1>
</div>
<div class='content'>
<h2>Neue Buchungsanfrage!</h2>
<p><strong>$veranstalterName</strong> möchte $bandName für den <strong>$eventDate</strong> buchen.</p>
<p style='text-align: center;'>
<a href='" . BASE_URL . "/profil.php' class='button'>Anfrage ansehen</a>
</p>
</div>
</div>
</body>
</html>
";
return sendEmail($bandEmail, $subject, $body);
}
?>
📁 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'];
}
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login / Registrierung - GetYourBand.ch</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body class="auth-page">
<div class="auth-container">
<div class="auth-header">
<h1>🎸 GetYourBand.ch</h1>
<p>Die funky Bandvermittlung</p>
</div>
<?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>
</body>
</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.';
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>E-Mail Verifizierung - GetYourBand.ch</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body class="auth-page">
<div class="auth-container">
<div class="auth-header">
<h1>🎸 GetYourBand.ch</h1>
</div>
<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>
</body>
</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 = '
<!DOCTYPE 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>
<body>
<div class="container">
<div class="header">
<h1 style="margin:0; color: #1a1a1a;">🎸 GetYourBand.ch</h1>
</div>
<div class="content">
<h2>Willkommen bei GetYourBand.ch!</h2>
<p>Danke für deine Registrierung. Bitte bestätige deine E-Mail-Adresse, um deinen Account zu aktivieren:</p>
<p style="text-align: center;">
<a href="' . $verifyLink . '" class="button">E-Mail bestätigen</a>
</p>
<p style="color: #888; font-size: 14px;">Oder kopiere diesen Link in deinen Browser:<br>
<a href="' . $verifyLink . '" style="color: #FFD700;">' . $verifyLink . '</a></p>
<p style="margin-top: 30px; color: #888; font-size: 12px;">Falls du dich nicht registriert hast, ignoriere diese E-Mail.</p>
</div>
<div class="footer">
© 2025 GetYourBand.ch - Die funky Bandvermittlung
</div>
</div>
</body>
</html>
';
return sendEmail($email, $subject, $html);
}
/**
* Passwort-Reset-E-Mail
*/
function sendPasswordResetEmail($email, $resetLink) {
$subject = '🔐 Passwort zurücksetzen - GetYourBand.ch';
$html = '
<!DOCTYPE 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; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1 style="margin:0; color: #1a1a1a;">🎸 GetYourBand.ch</h1>
</div>
<div class="content">
<h2>Passwort zurücksetzen</h2>
<p>Du hast eine Anfrage zum Zurücksetzen deines Passworts gestellt. Klicke auf den Button, um ein neues Passwort zu vergeben:</p>
<p style="text-align: center;">
<a href="' . $resetLink . '" class="button">Neues Passwort setzen</a>
</p>
<p style="color: #888; font-size: 14px;">Link gültig für 24 Stunden.</p>
<p style="margin-top: 30px; color: #888; font-size: 12px;">Falls du diese Anfrage nicht gestellt hast, ignoriere diese E-Mail.</p>
</div>
</div>
</body>
</html>
';
return sendEmail($email, $subject, $html);
}
/**
* Benachrichtigung an Band bei neuer Anfrage
*/
function sendAnfrageNotification($bandEmail, $bandName, $veranstalterName, $eventDate) {
$subject = '🎉 Neue Buchungsanfrage für ' . $bandName;
$html = '
<!DOCTYPE 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; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1 style="margin:0; color: #1a1a1a;">🎸 GetYourBand.ch</h1>
</div>
<div class="content">
<h2>Neue Buchungsanfrage!</h2>
<p><strong>' . sanitize($veranstalterName) . '</strong> möchte euch für den <strong>' . date('d.m.Y', strtotime($eventDate)) . '</strong> buchen.</p>
<p style="text-align: center;">
<a href="' . BASE_URL . '/profil.php" class="button">Anfrage ansehen</a>
</p>
</div>
</div>
</body>
</html>
';
return sendEmail($bandEmail, $subject, $html);
}
?>
📁 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'];
}
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login / Registrierung - GetYourBand.ch</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<div class="auth-container">
<div class="auth-header">
<h1>🎸 GetYourBand.ch</h1>
<p>Die funky Bandvermittlung</p>
</div>
<?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>
</body>
</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.';
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>E-Mail Verifizierung - GetYourBand.ch</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<div class="verify-container">
<div class="verify-box">
<h1>🎸 GetYourBand.ch</h1>
<?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>
</body>
</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);
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GetYourBand.ch - Die funky Bandvermittlung</title>
<link rel="stylesheet" href="assets/css/style.css">
<link rel="stylesheet" href="assets/css/bands.css">
</head>
<body>
<!-- Header -->
<header class="main-header">
<div class="container">
<div class="header-content">
<div class="logo">
<h1>🎸 GetYourBand.ch</h1>
<p>Die funky Bandvermittlung</p>
</div>
<nav class="main-nav">
<a href="index.php" class="nav-link active">Bands</a>
<?php if (isLoggedIn()): ?>
<?php if (isBand()): ?>
<a href="profil.php" class="nav-link">Mein Profil</a>
<?php endif; ?>
<?php if (isAdmin()): ?>
<a href="admin/dashboard.php" class="nav-link">Admin</a>
<?php endif; ?>
<a href="logout.php" class="nav-link">Logout</a>
<?php else: ?>
<a href="login.php" class="btn btn-primary">Login / Registrieren</a>
<?php endif; ?>
</nav>
</div>
</div>
</header>
<!-- 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>&copy; 2025 GetYourBand.ch - Alle Rechte vorbehalten</p>
</div>
</div>
</footer>
<script src="assets/js/app.js"></script>
</body>
</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'];
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= sanitize($band['band_name']) ?> - GetYourBand.ch</title>
<link rel="stylesheet" href="assets/css/style.css">
<link rel="stylesheet" href="assets/css/bands.css">
<link rel="stylesheet" href="assets/css/calendar.css">
</head>
<body>
<!-- Header -->
<header class="main-header">
<div class="container">
<div class="header-content">
<div class="logo">
<h1>🎸 GetYourBand.ch</h1>
</div>
<nav class="main-nav">
<a href="index.php" class="nav-link">← Zurück zur Übersicht</a>
<?php if (isLoggedIn()): ?>
<a href="logout.php" class="nav-link">Logout</a>
<?php else: ?>
<a href="login.php" class="btn btn-primary">Login</a>
<?php endif; ?>
</nav>
</div>
</div>
</header>
<!-- 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>&copy; 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>
</body>
</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.