This commit is contained in:
2025-11-19 00:49:53 +01:00
parent 553fefbeaa
commit 0834e1f733
12 changed files with 725 additions and 524 deletions

View File

@@ -1,177 +1,201 @@
<?php <?php
// public/account.php // public/account.php
declare(strict_types=1);
require_once __DIR__ . '/../src/auth.php'; require __DIR__ . '/../src/auth.php';
$lang = auth_get_lang();
auth_require_login(); auth_require_login();
$lang = auth_get_lang();
$user = auth_current_user(); $user = auth_current_user();
$csrfToken = auth_csrf_token(); $errors = [];
$flashSuccess = '';
$flashError = '';
$profileErrors = []; if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$profileSuccess = false; if (!auth_verify_csrf($_POST['_csrf'] ?? null)) {
$flashError = 'Sicherheitsfehler. Bitte Formular erneut absenden.';
// --- Profil-Update (Name + Sprache) ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'update_profile') {
if (!auth_verify_csrf($_POST['csrf_token'] ?? null)) {
$profileErrors['csrf'] = 'Deine Sitzung ist abgelaufen. Bitte Seite neu laden.';
} else { } else {
$fullName = $_POST['full_name'] ?? ''; $fullName = $_POST['full_name'] ?? ($user['full_name'] ?? '');
$preferredLang = $_POST['preferred_lang'] ?? $lang; $preferredLang = $_POST['preferred_lang'] ?? ($user['preferred_lang'] ?? $lang);
$result = auth_update_profile((int)$user['id'], $fullName, $preferredLang); $result = auth_update_profile((int)$user['id'], $fullName, $preferredLang);
if ($result['success']) {
$profileSuccess = true; if ($result['success'] ?? false) {
$flashSuccess = 'Profil wurde aktualisiert.';
$user = auth_current_user(); // neu laden $user = auth_current_user(); // neu laden
$lang = auth_get_lang(); // kann sich geändert haben
} else { } else {
$profileErrors = $result['errors']; $errors = $result['errors'] ?? [];
} }
} }
} }
// --- Avatar-Initialen --- $csrfToken = auth_csrf_token();
$initials = auth_user_initials($user); $initials = auth_user_initials($user);
$avatarUrl = auth_user_avatar_url($user); $avatarUrl = auth_user_avatar_url($user);
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="<?php echo htmlspecialchars($lang, ENT_QUOTES); ?>"> <html lang="<?php echo htmlspecialchars($lang, ENT_QUOTES, 'UTF-8'); ?>">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Mein Konto USBCheck</title> <title>Mein Konto usbcheck.it</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Fonts --> <!-- Fonts: Montserrat + Inter -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&family=Montserrat:wght@600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&family=Montserrat:wght@600;700&display=swap" rel="stylesheet">
<!-- Main CSS --> <!-- Main stylesheet -->
<link rel="stylesheet" href="/assets/css/main.css?v=1"> <link rel="stylesheet" href="/assets/css/main.css?v=1">
</head> </head>
<body> <body>
<?php <?php include __DIR__ . '/partials/header.php'; ?>
$langVar = $lang;
include __DIR__ . '/partials/header.php';
?>
<main class="page-main"> <main class="account-page">
<section class="section">
<div class="container"> <div class="container">
<h1 class="section-title" data-i18n="account_title">Mein Konto</h1> <div class="account-grid">
<p class="section-lead" data-i18n="account_intro"> <!-- Profil -->
Verwalte deine Profildaten und behalte deine USB-Tests im Überblick. <section class="account-card">
<h1 class="account-title">Mein Profil</h1>
<p class="account-subtitle">
Passe deinen Namen und deine bevorzugte Sprache an. Der Avatar wird aktuell aus deinen Initialen generiert.
</p> </p>
<div class="account-layout"> <?php if ($flashSuccess): ?>
<!-- Sidebar: Avatar + Basisinfos --> <div class="auth-flash-success">
<aside class="account-sidebar"> <?php echo htmlspecialchars($flashSuccess, ENT_QUOTES, 'UTF-8'); ?>
<div class="account-avatar">
<?php if ($avatarUrl): ?>
<img src="<?php echo htmlspecialchars($avatarUrl, ENT_QUOTES); ?>" alt="Avatar">
<?php else: ?>
<div class="avatar-circle">
<span><?php echo htmlspecialchars($initials, ENT_QUOTES); ?></span>
</div>
<?php endif; ?>
</div>
<div class="account-basic-info">
<h2><?php echo htmlspecialchars($user['full_name'] ?? $user['username'], ENT_QUOTES); ?></h2>
<p class="muted">
<?php echo htmlspecialchars($user['email'], ENT_QUOTES); ?>
</p>
<p class="muted">
Username: <?php echo htmlspecialchars($user['username'], ENT_QUOTES); ?>
</p>
</div>
<div class="account-links">
<a class="btn btn-outline" href="/fakecheck/?lang=<?php echo urlencode($lang); ?>" data-i18n="account_start_test">
USB-Test starten
</a>
<a class="btn btn-ghost" href="/logout.php?lang=<?php echo urlencode($lang); ?>" data-i18n="account_logout">
Abmelden
</a>
</div>
</aside>
<!-- Main: Profilformular + Platzhalter für später -->
<section class="account-main">
<div class="card">
<h2 data-i18n="account_profile_heading">Profil</h2>
<?php if (!empty($profileErrors['csrf'])): ?>
<div class="alert alert-error">
<?php echo htmlspecialchars($profileErrors['csrf'], ENT_QUOTES); ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php if ($profileSuccess): ?> <?php if ($flashError): ?>
<div class="alert alert-success" data-i18n="account_profile_updated"> <div class="auth-flash-error">
Profil wurde aktualisiert. <?php echo htmlspecialchars($flashError, ENT_QUOTES, 'UTF-8'); ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<form method="post" action="/account.php?lang=<?php echo urlencode($lang); ?>"> <form method="post" novalidate>
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES); ?>"> <input type="hidden" name="_csrf" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="action" value="update_profile">
<div class="form-row"> <div class="form-row">
<label for="full_name" data-i18n="account_full_name_label">Vollständiger Name</label> <label class="form-label" for="email">E-Mail-Adresse</label>
<input <input
class="form-input"
type="email"
id="email"
name="email"
value="<?php echo htmlspecialchars($user['email'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
readonly
>
<div class="form-help">
E-Mail-Änderungen bitte später über einen separaten Flow.
</div>
</div>
<div class="form-row">
<label class="form-label" for="username">Benutzername</label>
<input
class="form-input"
type="text"
id="username"
name="username"
value="<?php echo htmlspecialchars($user['username'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
readonly
>
<div class="form-help">
Benutzername ist aktuell nicht änderbar.
</div>
</div>
<div class="form-row">
<label class="form-label" for="full_name">Vollständiger Name</label>
<input
class="form-input"
type="text" type="text"
id="full_name" id="full_name"
name="full_name" name="full_name"
required required
value="<?php echo htmlspecialchars($user['full_name'] ?? '', ENT_QUOTES); ?>" value="<?php echo htmlspecialchars($_POST['full_name'] ?? ($user['full_name'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>"
> >
<?php if (!empty($profileErrors['full_name'])): ?> <?php if (!empty($errors['full_name'])): ?>
<p class="form-error"><?php echo htmlspecialchars($profileErrors['full_name'], ENT_QUOTES); ?></p> <div class="form-error"><?php echo htmlspecialchars($errors['full_name'], ENT_QUOTES, 'UTF-8'); ?></div>
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="preferred_lang" data-i18n="account_lang_label">Bevorzugte Sprache</label> <label class="form-label" for="preferred_lang">Bevorzugte Sprache</label>
<select id="preferred_lang" name="preferred_lang"> <select class="form-select" id="preferred_lang" name="preferred_lang">
<?php <?php
$pl = $user['preferred_lang'] ?? $lang; $selLang = $_POST['preferred_lang'] ?? ($user['preferred_lang'] ?? $lang);
$opts = [
'de' => 'Deutsch',
'en' => 'English',
'it' => 'Italiano',
'fr' => 'Français',
];
foreach ($opts as $code => $label) {
$selected = ($code === $selLang) ? 'selected' : '';
echo '<option value="' . htmlspecialchars($code, ENT_QUOTES, 'UTF-8') . '" ' . $selected . '>'
. htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</option>';
}
?> ?>
<option value="de" <?php echo $pl === 'de' ? 'selected' : ''; ?>>Deutsch</option>
<option value="en" <?php echo $pl === 'en' ? 'selected' : ''; ?>>English</option>
<option value="it" <?php echo $pl === 'it' ? 'selected' : ''; ?>>Italiano</option>
<option value="fr" <?php echo $pl === 'fr' ? 'selected' : ''; ?>>Français</option>
</select> </select>
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-primary" data-i18n="account_profile_save"> <button type="submit" class="btn btn-primary">
Änderungen speichern Änderungen speichern
</button> </button>
<a href="/logout.php" class="auth-link">
Logout
</a>
</div> </div>
</form> </form>
</div> </section>
<div class="card card-muted"> <!-- Avatar / Meta -->
<h2 data-i18n="account_usb_heading">Deine USB-Sticks & Testergebnisse</h2> <section class="account-card">
<p class="muted" data-i18n="account_usb_placeholder"> <h2 class="account-title">Avatar &amp; Konto</h2>
Hier wirst du später eine Übersicht deiner registrierten USB-Sticks und Testergebnisse sehen. <p class="account-subtitle">
Dein Avatar wird aktuell aus deinen Initialen erzeugt. Später kannst du hier ein eigenes Bild hochladen.
</p> </p>
<div class="account-avatar-preview">
<?php if ($avatarUrl): ?>
<div class="user-avatar" style="background-image:url('<?php echo htmlspecialchars($avatarUrl, ENT_QUOTES, 'UTF-8'); ?>'); background-size:cover; background-position:center; color:transparent;">
&nbsp;
</div>
<?php else: ?>
<div class="user-avatar">
<?php echo htmlspecialchars($initials, ENT_QUOTES, 'UTF-8'); ?>
</div>
<?php endif; ?>
<div class="account-avatar-note">
<strong>Avatar-Vorschau</strong><br>
Standardmäßig Initialen aus deinem Namen.<br>
Upload-Funktion folgt in einem späteren Schritt.
</div>
</div> </div>
<div class="card card-muted"> <div class="account-meta" style="margin-top:1.5rem;">
<h2 data-i18n="account_avatar_heading">Avatar</h2> <p><strong>Account-ID:</strong> <?php echo (int)$user['id']; ?></p>
<p class="muted" data-i18n="account_avatar_placeholder"> <p><strong>Registriert am:</strong>
Standardmäßig verwenden wir deine Initialen. Später kannst du hier ein eigenes Profilbild hochladen. <?php
if (!empty($user['created_at'])) {
echo htmlspecialchars($user['created_at'], ENT_QUOTES, 'UTF-8');
} else {
echo '';
}
?>
</p> </p>
</div> </div>
</section> </section>
</div> </div>
</div> </div>
</section>
</main> </main>
<?php include __DIR__ . '/partials/footer.php'; ?>
<script src="/assets/js/lang.js?v=1"></script> <script src="/assets/js/lang.js?v=1"></script>
</body> </body>
</html> </html>

View File

@@ -553,3 +553,189 @@ body {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }
/* Auth / Forms / Account */
.auth-page {
padding: 4rem 0;
}
.auth-layout {
max-width: 480px;
margin: 0 auto;
}
.auth-card {
background: #fff;
border-radius: 18px;
padding: 1.6rem 1.8rem;
box-shadow: var(--shadow-soft);
border: 1px solid rgba(200, 203, 208, 0.5);
}
.auth-title {
font-family: 'Montserrat', system-ui, sans-serif;
font-weight: 700;
font-size: 1.5rem;
margin: 0 0 0.35rem;
color: var(--deep-gray);
}
.auth-subtitle {
font-size: 0.95rem;
color: #555;
margin: 0 0 1.5rem;
}
.auth-flash-success,
.auth-flash-error {
border-radius: 12px;
padding: 0.75rem 0.9rem;
font-size: 0.9rem;
margin-bottom: 1rem;
}
.auth-flash-success {
background: #e6f9ef;
border: 1px solid rgba(3, 193, 96, 0.4);
color: #145c32;
}
.auth-flash-error {
background: #fde7ea;
border: 1px solid rgba(230, 57, 70, 0.4);
color: #7f1d1d;
}
.form-row {
margin-bottom: 1rem;
}
.form-label {
display: block;
font-size: 0.85rem;
color: #555;
margin-bottom: 0.3rem;
}
.form-input,
.form-select,
.form-textarea {
width: 100%;
border-radius: 10px;
border: 1px solid var(--silver);
padding: 0.55rem 0.7rem;
font-size: 0.95rem;
font-family: inherit;
transition: border-color 0.12s ease, box-shadow 0.12s ease, background-color 0.12s ease;
background: #fff;
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: var(--brand-blue);
box-shadow: 0 0 0 1px rgba(0, 81, 255, 0.15);
background: #fff;
}
.form-input[readonly],
.form-select[readonly] {
background: #f3f4f6;
color: #666;
}
.form-error {
margin-top: 0.3rem;
font-size: 0.8rem;
color: var(--error-red);
}
.form-help {
margin-top: 0.3rem;
font-size: 0.8rem;
color: #777;
}
.form-actions {
margin-top: 1.5rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}
.auth-link {
font-size: 0.9rem;
color: var(--brand-blue);
text-decoration: none;
}
.auth-link:hover {
text-decoration: underline;
}
/* Account page */
.account-page {
padding: 4rem 0;
}
.account-grid {
display: grid;
grid-template-columns: minmax(0, 2fr) minmax(0, 1.2fr);
gap: 2rem;
}
.account-card {
background: #fff;
border-radius: 18px;
padding: 1.6rem 1.8rem;
box-shadow: var(--shadow-soft);
border: 1px solid rgba(200, 203, 208, 0.5);
}
.account-title {
font-family: 'Montserrat', system-ui, sans-serif;
font-weight: 700;
font-size: 1.4rem;
margin: 0 0 0.4rem;
color: var(--deep-gray);
}
.account-subtitle {
margin: 0 0 1.4rem;
font-size: 0.95rem;
color: #555;
}
.account-meta {
font-size: 0.85rem;
color: #777;
}
.account-avatar-preview {
display: flex;
align-items: center;
gap: 1rem;
margin-top: 0.75rem;
}
.account-avatar-preview .user-avatar {
width: 44px;
height: 44px;
font-size: 1.05rem;
}
.account-avatar-note {
font-size: 0.85rem;
color: #666;
}
@media (max-width: 900px) {
.account-grid {
grid-template-columns: 1fr;
}
}

View File

@@ -3,6 +3,18 @@
(function () { (function () {
const translations = { const translations = {
de: { de: {
header_slogan: "USB-Sticks testen",
btn_login: "Login",
nav_how: "Ablauf",
nav_problem: "Problem",
nav_features: "Funktionen",
nav_security: "Sicherheit",
nav_faq: "FAQ",
footer_imprint: "Impressum",
footer_privacy: "Datenschutz",
brand_wordmark: "usbcheck.it", brand_wordmark: "usbcheck.it",
brand_subtitle: "USB-Sticks auf Fakes testen", brand_subtitle: "USB-Sticks auf Fakes testen",
btn_login: "Login", btn_login: "Login",
@@ -118,6 +130,17 @@
}, },
en: { en: {
header_slogan: "Test USB drives",
btn_login: "Login",
nav_how: "How it works",
nav_problem: "Why it matters",
nav_features: "Features",
nav_security: "Security",
nav_faq: "FAQ",
footer_imprint: "Imprint",
footer_privacy: "Privacy policy",
brand_wordmark: "usbcheck.it", brand_wordmark: "usbcheck.it",
brand_subtitle: "Test USB drives for fakes", brand_subtitle: "Test USB drives for fakes",
btn_login: "Login", btn_login: "Login",
@@ -234,6 +257,17 @@
// Italienisch (kurz, sachlich) // Italienisch (kurz, sachlich)
it: { it: {
header_slogan: "Test delle chiavette USB",
btn_login: "Accesso",
nav_how: "Come funziona",
nav_problem: "Perché è importante",
nav_features: "Funzioni",
nav_security: "Sicurezza",
nav_faq: "FAQ",
footer_imprint: "Imprint",
footer_privacy: "Privacy",
brand_wordmark: "usbcheck.it", brand_wordmark: "usbcheck.it",
brand_subtitle: "Controlla le chiavette USB contraffatte", brand_subtitle: "Controlla le chiavette USB contraffatte",
btn_login: "Login", btn_login: "Login",
@@ -351,6 +385,17 @@
// Französisch (kurz, sachlich) // Französisch (kurz, sachlich)
fr: { fr: {
header_slogan: "Tester vos clés USB",
btn_login: "Connexion",
nav_how: "Fonctionnement",
nav_problem: "Problème",
nav_features: "Fonctionnalités",
nav_security: "Sécurité",
nav_faq: "FAQ",
footer_imprint: "Mentions légales",
footer_privacy: "Confidentialité",
brand_wordmark: "usbcheck.it", brand_wordmark: "usbcheck.it",
brand_subtitle: "Tester les clés USB contrefaites", brand_subtitle: "Tester les clés USB contrefaites",
btn_login: "Connexion", btn_login: "Connexion",

View File

@@ -1,124 +1,42 @@
<?php <?php
// public/index.php // Sprachlogik:
?> $lang = $_GET['lang'] ?? 'en';
<!DOCTYPE html> $lang = in_array($lang, ['de','en','it','fr']) ? $lang : 'en';
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>USBCheck Test USB-Sticks auf Fakes</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Prüfe deine USB-Sticks auf Fakes, langsame Geschwindigkeit und Datenverlust. USBCheck bietet einen kostenlosen Browser-Schnelltest und einen Pro-Modus für Profis."> // User-Dummy (später über Login ersetzen)
$userInitials = null;
?>
<!DOCTYPE html>
<html lang="<?= htmlspecialchars($lang) ?>">
<head>
<meta charset="UTF-8">
<title>usbcheck.it Test USB-Sticks</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Description -->
<meta name="description" content="Prüfe deine USB-Sticks auf Geschwindigkeit, Integrität und mögliche Fakes direkt im Browser.">
<!-- Fonts --> <!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Montserrat:wght@600;700;800&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Montserrat:wght@600;700;800&display=swap" rel="stylesheet">
<!-- Tailwind CDN (für Dev; später durch eigenes CSS ersetzbar) --> <!-- Tailwind (Dev) -->
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = { <!-- Language JS -->
theme: { <script src="/assets/js/lang.js?v=1" defer></script>
extend: {
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
heading: ['Montserrat', 'system-ui', 'sans-serif'],
},
colors: {
brand: {
bg: '#1A1A1A', // deep_gray
surface: '#232323', // dunkles Panel
primary: '#0051FF', // brand_blue
primarySoft: '#0b1535',
text: '#FAFAFA',
muted: '#C8CBD0', // silver
border: '#333333',
}
},
boxShadow: {
'soft': '0 18px 45px rgba(0,0,0,0.55)',
},
borderRadius: {
'xl2': '1.25rem',
}
}
}
}
</script>
</head> </head>
<body class="bg-brand-bg text-brand-text font-sans antialiased scroll-smooth"> <body class="bg-brand-bg text-brand-text font-sans antialiased scroll-smooth">
<!-- Page Wrapper -->
<div class="min-h-screen flex flex-col"> <div class="min-h-screen flex flex-col">
<!-- Header -->
<header class="sticky top-0 z-40 border-b border-brand-border/70 backdrop-blur bg-brand-bg/85">
<div class="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8 flex items-center justify-between h-16">
<!-- Logo & Brand -->
<div class="flex items-center gap-3">
<a href="#hero" class="flex items-center gap-3">
<!-- Logo Pfad: public/img/logo_slogan.png -->
<img src="/img/logo_slogan.png" alt="usbcheck.it Logo" class="h-9 w-auto">
<div class="hidden sm:flex flex-col leading-tight">
<span class="font-heading font-bold text-sm uppercase tracking-[0.18em] text-brand-muted" data-i18n="brand_wordmark">
usbcheck.it
</span>
<span class="text-xs text-brand-muted" data-i18n="brand_subtitle">
Test USB drives for fakes
</span>
</div>
</a>
</div>
<!-- Navigation + Lang + Login --> <!-- HEADER -->
<div class="flex items-center gap-4 sm:gap-6"> <?php include __DIR__ . "/partials/header.php"; ?>
<nav class="hidden md:flex items-center gap-6 text-xs font-medium text-brand-muted uppercase tracking-[0.18em]">
<a href="#how" class="hover:text-brand-primary transition-colors" data-i18n="nav_how">
How it works
</a>
<a href="#problem" class="hover:text-brand-primary transition-colors" data-i18n="nav_problem">
Why it matters
</a>
<a href="#features" class="hover:text-brand-primary transition-colors" data-i18n="nav_features">
Features
</a>
<a href="#security" class="hover:text-brand-primary transition-colors" data-i18n="nav_security">
Security
</a>
<a href="#faq" class="hover:text-brand-primary transition-colors" data-i18n="nav_faq">
FAQ
</a>
</nav>
<!-- Language Switch --> <!-- MAIN CONTENT -->
<div class="flex items-center gap-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-brand-muted">
<button class="lang-pill px-2 py-1 rounded-full border border-transparent hover:border-brand-primary hover:text-brand-primary transition-colors"
data-lang="de">DE</button>
<button class="lang-pill px-2 py-1 rounded-full border border-transparent hover:border-brand-primary hover:text-brand-primary transition-colors"
data-lang="en">EN</button>
<button class="lang-pill px-2 py-1 rounded-full border border-transparent hover:border-brand-primary hover:text-brand-primary transition-colors"
data-lang="it">IT</button>
<button class="lang-pill px-2 py-1 rounded-full border border-transparent hover:border-brand-primary hover:text-brand-primary transition-colors"
data-lang="fr">FR</button>
</div>
<!-- Login Button / Avatar -->
<button id="loginButton"
class="relative inline-flex items-center justify-center rounded-full bg-brand-primary px-4 py-1.5 text-xs font-semibold uppercase tracking-[0.18em] text-brand-bg shadow-soft hover:bg-blue-400 transition-colors">
<span id="loginLabel" data-i18n="btn_login">Login</span>
</button>
<!-- Avatar (wird per JS ein-/ausgeblendet) -->
<button id="userAvatar"
class="hidden h-9 w-9 rounded-full border border-brand-border bg-brand-surface flex items-center justify-center text-xs font-semibold text-brand-text shadow-soft hover:border-brand-primary transition"
aria-label="Mein Konto">
<span id="avatarInitials">UC</span>
</button>
</div>
</div>
</header>
<!-- Main Content -->
<main class="flex-1"> <main class="flex-1">
<!-- Hero --> <!-- Hero -->
<section id="hero" class="relative overflow-hidden"> <section id="hero" class="relative overflow-hidden">
@@ -512,20 +430,11 @@
</section> </section>
</main> </main>
<!-- Footer --> <!-- FOOTER -->
<footer class="border-t border-brand-border/70"> <?php include __DIR__ . "/partials/footer.php"; ?>
<div class="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8 py-6 flex flex-col sm:flex-row items-center justify-between gap-4 text-xs text-brand-muted">
<p data-i18n="footer_copy">© <?php echo date('Y'); ?> usbcheck.it. All rights reserved.</p>
<div class="flex items-center gap-4">
<a href="/impressum.php" class="hover:text-brand-primary transition-colors" data-i18n="footer_imprint">Impressum</a>
<a href="/datenschutz.php" class="hover:text-brand-primary transition-colors" data-i18n="footer_privacy">Datenschutz</a>
</div>
</div>
</footer>
</div> </div>
<!-- I18n + Login/Avatar Logic --> <script src="/assets/js/header.js"></script>
<script src="/assets/js/lang.js?v=1"></script>
</body> </body>
</html> </html>

View File

@@ -1,50 +1,88 @@
<?php <?php
// public/login.php
declare(strict_types=1); declare(strict_types=1);
require __DIR__ . '/../config/db.php'; require __DIR__ . '/../src/auth.php'; // lädt auch config/db.php
require __DIR__ . '/../src/Auth.php';
$auth = new Auth($pdo); $lang = auth_get_lang();
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') { $errors = [];
$identifier = trim($_POST['identifier'] ?? ''); $globalError = '';
$password = $_POST['password'] ?? '';
if ($auth->login($identifier, $password)) { // Optional: Redirect-Ziel (z. B. ?redirect=/account.php)
header('Location: /'); // nach Login auf Startseite $redirect = '/account.php';
exit; if (!empty($_GET['redirect']) && is_string($_GET['redirect'])) {
} else { // Nur interne Pfade erlauben, keine kompletten URLs
$error = 'Login fehlgeschlagen. Bitte Zugangsdaten prüfen.'; if (strpos($_GET['redirect'], '/') === 0) {
$redirect = $_GET['redirect'];
} }
} }
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// CSRF prüfen
if (!auth_verify_csrf($_POST['csrf_token'] ?? null)) {
$globalError = 'Sicherheitsfehler. Bitte die Seite neu laden und erneut versuchen.';
} else {
$identifier = trim((string)($_POST['identifier'] ?? ''));
$password = (string)($_POST['password'] ?? '');
$result = auth_login($identifier, $password);
if ($result['success'] === true) {
header('Location: ' . $redirect);
exit;
} else {
$errors = $result['errors'] ?? [];
}
}
}
$csrfToken = auth_csrf_token();
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> <html lang="<?php echo htmlspecialchars($lang, ENT_QUOTES, 'UTF-8'); ?>">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Login usbcheck.it</title> <title>Login usbcheck.it</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Dein Tailwind CSS -->
<link rel="stylesheet" href="/css/tailwind.css">
</head>
<body class="bg-[#FAFAFA] text-[#1A1A1A] font-[Inter]">
<div class="min-h-screen flex items-center justify-center px-4">
<div class="w-full max-w-md bg-white shadow-lg rounded-2xl p-8">
<h1 class="text-2xl font-[Montserrat] font-bold mb-6 text-center">
Anmelden bei <span class="text-[#0051FF]">usbcheck.it</span>
</h1>
<?php if ($error): ?> <!-- Fonts: Montserrat + Inter -->
<div class="mb-4 text-sm text-[#E63946]"> <link rel="preconnect" href="https://fonts.googleapis.com">
<?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&family=Montserrat:wght@600;700&display=swap" rel="stylesheet">
<!-- Haupt-CSS -->
<link rel="stylesheet" href="/assets/css/main.css?v=1">
</head>
<body>
<?php include __DIR__ . '/partials/header.php'; ?>
<main>
<section class="section">
<div class="container">
<div style="max-width: 480px; margin: 0 auto;">
<h1 class="section-title" style="font-size: 1.6rem; text-align: center; margin-bottom: 1.5rem;">
Anmelden bei usbcheck.it
</h1>
<p class="section-lead" style="text-align: center; margin-bottom: 2rem;">
Melde dich mit deiner E-Mail-Adresse oder deinem Benutzernamen an, um deine USB-Tests und Geräte zu verwalten.
</p>
<?php if ($globalError !== ''): ?>
<div style="margin-bottom: 1rem; padding: 0.75rem 1rem; border-radius: 12px; background: #ffe6e8; color: #a4001f; font-size: 0.9rem;">
<?php echo htmlspecialchars($globalError, ENT_QUOTES, 'UTF-8'); ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<form method="post" class="space-y-4"> <?php if (!empty($errors['login'])): ?>
<div> <div style="margin-bottom: 1rem; padding: 0.75rem 1rem; border-radius: 12px; background: #ffe6e8; color: #a4001f; font-size: 0.9rem;">
<label class="block text-sm mb-1" for="identifier"> <?php echo htmlspecialchars($errors['login'], ENT_QUOTES, 'UTF-8'); ?>
</div>
<?php endif; ?>
<form method="post" class="step-card">
<div style="margin-bottom: 1rem;">
<label for="identifier" style="display:block; font-size:0.9rem; margin-bottom:0.25rem;">
E-Mail oder Benutzername E-Mail oder Benutzername
</label> </label>
<input <input
@@ -52,12 +90,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
id="identifier" id="identifier"
name="identifier" name="identifier"
required required
class="w-full border border-[#C8CBD0] rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#0051FF]" style="
width: 100%;
border-radius: 10px;
border: 1px solid var(--silver);
padding: 0.55rem 0.75rem;
font-size: 0.95rem;
font-family: 'Inter', system-ui, sans-serif;
"
value="<?php echo htmlspecialchars($_POST['identifier'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
> >
</div> </div>
<div> <div style="margin-bottom: 1rem;">
<label class="block text-sm mb-1" for="password"> <label for="password" style="display:block; font-size:0.9rem; margin-bottom:0.25rem;">
Passwort Passwort
</label> </label>
<input <input
@@ -65,18 +111,36 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
id="password" id="password"
name="password" name="password"
required required
class="w-full border border-[#C8CBD0] rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#0051FF]" style="
width: 100%;
border-radius: 10px;
border: 1px solid var(--silver);
padding: 0.55rem 0.75rem;
font-size: 0.95rem;
font-family: 'Inter', system-ui, sans-serif;
"
> >
</div> </div>
<button <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
type="submit"
class="w-full bg-[#0051FF] text-white font-[Montserrat] font-semibold rounded-lg py-2 mt-4 hover:bg-blue-700 transition" <div style="margin-top: 1.5rem; display:flex; flex-direction:column; gap:0.5rem;">
> <button type="submit" class="btn btn-primary" style="width: 100%; justify-content: center;">
Login Login
</button> </button>
<a href="/register.php" class="btn btn-ghost" style="width: 100%; justify-content: center;">
Noch kein Konto? Jetzt registrieren
</a>
</div>
</form> </form>
</div> </div>
</div> </div>
</section>
</main>
<?php include __DIR__ . '/partials/footer.php'; ?>
<script src="/assets/js/lang.js?v=1"></script>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,13 @@
<?php
// public/partials/footer.php
?>
<footer class="border-t border-brand-border/70">
<div class="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8 py-6 flex flex-col sm:flex-row items-center justify-between gap-4 text-xs text-brand-muted">
<p>© <?= date('Y') ?> usbcheck.it</p>
<div class="flex items-center gap-4">
<a href="/impressum.php" class="hover:text-brand-primary transition-colors" data-i18n="footer_imprint"></a>
<a href="/datenschutz.php" class="hover:text-brand-primary transition-colors" data-i18n="footer_privacy"></a>
</div>
</div>
</footer>

View File

@@ -1,75 +1,62 @@
<?php <?php
// public/partials/header.php // public/partials/header.php
// Fallback, falls $currentLang nicht gesetzt ist
if (!isset($currentLang)) {
$currentLang = 'en';
}
$supportedLangs = [
'de' => 'Deutsch',
'en' => 'English',
'it' => 'Italiano',
'fr' => 'Français',
];
/**
* Baut eine URL und hängt immer ?lang=<code> dran.
* $path sollte mit / beginnen, z.B. "/", "/fakecheck/", "/impressum".
*/
function usbcheck_url_with_lang(string $path, string $lang): string
{
$path = $path ?: '/';
$separator = str_contains($path, '?') ? '&' : '?';
return $path . $separator . 'lang=' . urlencode($lang);
}
?> ?>
<header class="site-header"> <header class="sticky top-0 z-40 border-b border-brand-border/70 backdrop-blur bg-brand-bg/85">
<div class="container header-inner"> <div class="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8 flex items-center justify-between h-16">
<a href="<?php echo htmlspecialchars(usbcheck_url_with_lang('/', $currentLang)); ?>" class="logo-wrap"> <!-- Logo -->
<img src="/img/logo.png" alt="usbcheck.it Logo" class="logo-img"> <div class="flex items-center gap-3">
<a href="/?lang=<?= htmlspecialchars($lang) ?>" class="flex items-center gap-3">
<img src="/assets/img/logo_slogan.png" alt="usbcheck.it Logo" class="h-9 w-auto">
<div class="hidden sm:flex flex-col leading-tight">
<span class="font-heading font-bold text-sm uppercase tracking-[0.18em] text-brand-muted">
usbcheck.it
</span>
<span class="text-xs text-brand-muted" data-i18n="header_slogan">
Test USB drives
</span>
</div>
</a> </a>
</div>
<nav class="main-nav"> <!-- Navigation -->
<a href="<?php echo htmlspecialchars(usbcheck_url_with_lang('/', $currentLang)); ?>#how-it-works" <div class="flex items-center gap-6">
class="nav-link"
data-i18n="nav_how_it_works"></a>
<a href="<?php echo htmlspecialchars(usbcheck_url_with_lang('/', $currentLang)); ?>#features" <nav class="hidden md:flex items-center gap-6 text-xs font-medium text-brand-muted uppercase tracking-[0.18em]">
class="nav-link" <a href="#how" class="hover:text-brand-primary transition-colors" data-i18n="nav_how"></a>
data-i18n="nav_features"></a> <a href="#problem" class="hover:text-brand-primary transition-colors" data-i18n="nav_problem"></a>
<a href="#features" class="hover:text-brand-primary transition-colors" data-i18n="nav_features"></a>
<a href="<?php echo htmlspecialchars(usbcheck_url_with_lang('/', $currentLang)); ?>#pricing" <a href="#security" class="hover:text-brand-primary transition-colors" data-i18n="nav_security"></a>
class="nav-link" <a href="#faq" class="hover:text-brand-primary transition-colors" data-i18n="nav_faq"></a>
data-i18n="nav_pricing"></a>
<a href="<?php echo htmlspecialchars(usbcheck_url_with_lang('/', $currentLang)); ?>#faq"
class="nav-link"
data-i18n="nav_faq"></a>
</nav> </nav>
<div class="header-actions"> <!-- Language Switch -->
<div class="lang-switch" data-current-lang="<?php echo htmlspecialchars($currentLang); ?>"> <div class="relative">
<button id="lang-current" class="lang-current"> <button id="langCurrent"
<?php echo strtoupper(htmlspecialchars($currentLang)); ?> class="text-xs uppercase tracking-[0.18em] text-brand-muted hover:text-brand-primary transition">
<?= strtoupper($lang) ?>
</button> </button>
<div id="lang-menu" class="lang-menu hidden">
<?php foreach ($supportedLangs as $code => $label): ?> <div id="langMenu"
<button data-lang="<?php echo htmlspecialchars($code); ?>"> class="hidden absolute right-0 mt-2 w-28 rounded-md bg-brand-surface border border-brand-border shadow-lg p-2 text-xs">
<?php echo htmlspecialchars($label); ?> <a href="?lang=de" class="block py-1 hover:text-brand-primary">Deutsch</a>
</button> <a href="?lang=en" class="block py-1 hover:text-brand-primary">English</a>
<?php endforeach; ?> <a href="?lang=it" class="block py-1 hover:text-brand-primary">Italiano</a>
<a href="?lang=fr" class="block py-1 hover:text-brand-primary">Français</a>
</div> </div>
</div> </div>
<button id="login-button" <!-- Login Button / Avatar -->
class="btn btn-outline" <button id="loginButton"
data-i18n="btn_login"></button> class="relative inline-flex items-center justify-center rounded-full bg-brand-primary px-4 py-1.5 text-xs font-semibold uppercase tracking-[0.18em] text-brand-bg shadow-soft hover:bg-cyan-400 transition-colors"
data-i18n="btn_login">
</button>
<div id="user-avatar" class="user-avatar hidden" title="Account"> <button id="userAvatar"
<!-- Standard: Initialen, später durch Bild ersetzbar --> class="hidden h-9 w-9 rounded-full border border-brand-border bg-brand-surface flex items-center justify-center text-xs font-semibold text-brand-text shadow-soft hover:border-brand-primary transition"
<span id="user-avatar-initials">U</span> aria-label="Mein Konto">
</div> <span><?= strtoupper(substr($userInitials ?? 'U', 0, 2)) ?></span>
</button>
</div> </div>
</div> </div>
</header> </header>

View File

@@ -1,43 +1,25 @@
<?php <?php
// public/register.php // public/register.php
declare(strict_types=1);
require_once __DIR__ . '/../src/auth.php'; require __DIR__ . '/../src/auth.php';
$lang = auth_get_lang(); $lang = auth_get_lang();
$csrfToken = auth_csrf_token();
$currentUser = auth_current_user();
if ($currentUser) {
// Bereits eingeloggt -> Accountseite
header('Location: /account.php?lang=' . urlencode($lang));
exit;
}
$errors = []; $errors = [];
$values = [ $globalError = '';
'email' => '', $result = null;
'username' => '',
'full_name' => '',
'preferred_lang' => $lang,
];
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!auth_verify_csrf($_POST['csrf_token'] ?? null)) { if (!auth_verify_csrf($_POST['_csrf'] ?? null)) {
$errors['csrf'] = 'Deine Sitzung ist abgelaufen. Bitte Seite neu laden.'; $globalError = 'Sicherheitsfehler. Bitte Formular erneut absenden.';
} else { } else {
$email = $_POST['email'] ?? ''; $email = $_POST['email'] ?? '';
$username = $_POST['username'] ?? ''; $username = $_POST['username'] ?? '';
$fullName = $_POST['full_name'] ?? ''; $fullName = $_POST['full_name'] ?? '';
$password = $_POST['password'] ?? ''; $password = $_POST['password'] ?? '';
$passwordConfirm= $_POST['password_confirm'] ?? ''; $passwordConfirm = $_POST['password_confirm'] ?? '';
$preferredLang = $_POST['preferred_lang'] ?? $lang; $preferredLang = $_POST['preferred_lang'] ?? $lang;
$values = [
'email' => $email,
'username' => $username,
'full_name' => $fullName,
'preferred_lang' => $preferredLang,
];
$result = auth_register_user( $result = auth_register_user(
$email, $email,
$username, $username,
@@ -47,152 +29,169 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$preferredLang $preferredLang
); );
if ($result['success']) { if ($result['success'] ?? false) {
header('Location: /account.php?lang=' . urlencode($preferredLang)); // Direkt nach Account-Seite
header('Location: /account.php');
exit; exit;
} else {
$errors = array_merge($errors, $result['errors']);
} }
$errors = $result['errors'] ?? [];
} }
} }
$csrfToken = auth_csrf_token();
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="<?php echo htmlspecialchars($lang, ENT_QUOTES); ?>"> <html lang="<?php echo htmlspecialchars($lang, ENT_QUOTES, 'UTF-8'); ?>">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Registrierung USBCheck</title> <title>Registrieren usbcheck.it</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Fonts --> <!-- Fonts: Montserrat + Inter -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&family=Montserrat:wght@600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&family=Montserrat:wght@600;700&display=swap" rel="stylesheet">
<!-- Main CSS --> <!-- Main stylesheet -->
<link rel="stylesheet" href="/assets/css/main.css?v=1"> <link rel="stylesheet" href="/assets/css/main.css?v=1">
</head> </head>
<body> <body>
<?php <?php include __DIR__ . '/partials/header.php'; ?>
// Header-Partial, erwartet ggf. $lang
$langVar = $lang;
include __DIR__ . '/partials/header.php';
?>
<main class="page-main"> <main class="auth-page">
<section class="section"> <div class="container">
<div class="container narrow"> <div class="auth-layout">
<h1 class="section-title" data-i18n="register_title">Konto erstellen</h1> <div class="auth-card">
<p class="section-lead" data-i18n="register_intro"> <h1 class="auth-title">Konto erstellen</h1>
Erstelle ein kostenloses Konto, um deine USB-Tests zu verwalten. <p class="auth-subtitle">
Erstelle einen kostenlosen Account, um deine USB-Tests zu speichern und mehrere Sticks zu verwalten.
</p> </p>
<?php if (!empty($errors['csrf'])): ?> <?php if ($globalError): ?>
<div class="alert alert-error"> <div class="auth-flash-error">
<?php echo htmlspecialchars($errors['csrf'], ENT_QUOTES); ?> <?php echo htmlspecialchars($globalError, ENT_QUOTES, 'UTF-8'); ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<form class="form-card" method="post" action="/register.php?lang=<?php echo urlencode($lang); ?>"> <form method="post" novalidate>
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES); ?>"> <input type="hidden" name="_csrf" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
<div class="form-row"> <div class="form-row">
<label for="email" data-i18n="register_email_label">E-Mail-Adresse</label> <label class="form-label" for="email">E-Mail-Adresse</label>
<input <input
class="form-input"
type="email" type="email"
id="email" id="email"
name="email" name="email"
required required
value="<?php echo htmlspecialchars($values['email'], ENT_QUOTES); ?>" value="<?php echo htmlspecialchars($_POST['email'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
> >
<?php if (!empty($errors['email'])): ?> <?php if (!empty($errors['email'])): ?>
<p class="form-error"><?php echo htmlspecialchars($errors['email'], ENT_QUOTES); ?></p> <div class="form-error"><?php echo htmlspecialchars($errors['email'], ENT_QUOTES, 'UTF-8'); ?></div>
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="username" data-i18n="register_username_label">Username</label> <label class="form-label" for="username">Benutzername</label>
<input <input
class="form-input"
type="text" type="text"
id="username" id="username"
name="username" name="username"
required required
value="<?php echo htmlspecialchars($values['username'], ENT_QUOTES); ?>" value="<?php echo htmlspecialchars($_POST['username'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
> >
<?php if (!empty($errors['username'])): ?> <?php if (!empty($errors['username'])): ?>
<p class="form-error"><?php echo htmlspecialchars($errors['username'], ENT_QUOTES); ?></p> <div class="form-error"><?php echo htmlspecialchars($errors['username'], ENT_QUOTES, 'UTF-8'); ?></div>
<?php endif; ?> <?php endif; ?>
<div class="form-help">
332 Zeichen, Buchstaben/Zahlen/._- erlaubt.
</div>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="full_name" data-i18n="register_full_name_label">Vollständiger Name</label> <label class="form-label" for="full_name">Vollständiger Name</label>
<input <input
class="form-input"
type="text" type="text"
id="full_name" id="full_name"
name="full_name" name="full_name"
required required
value="<?php echo htmlspecialchars($values['full_name'], ENT_QUOTES); ?>" value="<?php echo htmlspecialchars($_POST['full_name'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
> >
<?php if (!empty($errors['full_name'])): ?> <?php if (!empty($errors['full_name'])): ?>
<p class="form-error"><?php echo htmlspecialchars($errors['full_name'], ENT_QUOTES); ?></p> <div class="form-error"><?php echo htmlspecialchars($errors['full_name'], ENT_QUOTES, 'UTF-8'); ?></div>
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="form-row form-row-inline"> <div class="form-row">
<div> <label class="form-label" for="password">Passwort</label>
<label for="password" data-i18n="register_password_label">Passwort</label>
<input <input
class="form-input"
type="password" type="password"
id="password" id="password"
name="password" name="password"
required required
minlength="10"
> >
<?php if (!empty($errors['password'])): ?> <?php if (!empty($errors['password'])): ?>
<p class="form-error"><?php echo htmlspecialchars($errors['password'], ENT_QUOTES); ?></p> <div class="form-error"><?php echo htmlspecialchars($errors['password'], ENT_QUOTES, 'UTF-8'); ?></div>
<?php endif; ?>
</div>
<div>
<label for="password_confirm" data-i18n="register_password_confirm_label">Passwort wiederholen</label>
<input
type="password"
id="password_confirm"
name="password_confirm"
required
minlength="10"
>
<?php if (!empty($errors['password_confirm'])): ?>
<p class="form-error"><?php echo htmlspecialchars($errors['password_confirm'], ENT_QUOTES); ?></p>
<?php endif; ?> <?php endif; ?>
<div class="form-help">
Mindestens 10 Zeichen. Bitte ein sicheres Passwort wählen.
</div> </div>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="preferred_lang" data-i18n="register_lang_label">Bevorzugte Sprache</label> <label class="form-label" for="password_confirm">Passwort wiederholen</label>
<select id="preferred_lang" name="preferred_lang"> <input
<option value="de" <?php echo $values['preferred_lang'] === 'de' ? 'selected' : ''; ?>>Deutsch</option> class="form-input"
<option value="en" <?php echo $values['preferred_lang'] === 'en' ? 'selected' : ''; ?>>English</option> type="password"
<option value="it" <?php echo $values['preferred_lang'] === 'it' ? 'selected' : ''; ?>>Italiano</option> id="password_confirm"
<option value="fr" <?php echo $values['preferred_lang'] === 'fr' ? 'selected' : ''; ?>>Français</option> name="password_confirm"
required
>
<?php if (!empty($errors['password_confirm'])): ?>
<div class="form-error"><?php echo htmlspecialchars($errors['password_confirm'], ENT_QUOTES, 'UTF-8'); ?></div>
<?php endif; ?>
</div>
<div class="form-row">
<label class="form-label" for="preferred_lang">Bevorzugte Sprache</label>
<select class="form-select" id="preferred_lang" name="preferred_lang">
<?php
$selLang = $_POST['preferred_lang'] ?? $lang;
$opts = [
'de' => 'Deutsch',
'en' => 'English',
'it' => 'Italiano',
'fr' => 'Français',
];
foreach ($opts as $code => $label) {
$selected = ($code === $selLang) ? 'selected' : '';
echo '<option value="' . htmlspecialchars($code, ENT_QUOTES, 'UTF-8') . '" ' . $selected . '>'
. htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</option>';
}
?>
</select> </select>
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-primary" data-i18n="register_submit"> <button type="submit" class="btn btn-primary">
Konto erstellen Konto erstellen
</button> </button>
<p class="form-hint"> <a class="auth-link" href="/login.php">
<span data-i18n="register_existing_hint">Du hast bereits ein Konto?</span> Bereits ein Konto? Login
<a href="/login.php?lang=<?php echo urlencode($lang); ?>" data-i18n="register_existing_link">
Zum Login
</a> </a>
</p>
</div> </div>
</form> </form>
</div> </div>
</section> </div>
</div>
</main> </main>
<?php include __DIR__ . '/partials/footer.php'; ?>
<script src="/assets/js/lang.js?v=1"></script> <script src="/assets/js/lang.js?v=1"></script>
</body> </body>
</html> </html>

View File

@@ -1,25 +0,0 @@
<?php
// src/Database.php
declare(strict_types=1);
class Database
{
private static ?PDO $pdo = null;
public static function getConnection(): PDO
{
if (self::$pdo === null) {
$config = require __DIR__ . '/../config/database.php';
self::$pdo = new PDO(
$config['dsn'],
$config['user'],
$config['password'],
$config['options']
);
}
return self::$pdo;
}
}

View File

@@ -1 +0,0 @@
demo