com
This commit is contained in:
@@ -1,177 +1,201 @@
|
||||
<?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();
|
||||
|
||||
$user = auth_current_user();
|
||||
$csrfToken = auth_csrf_token();
|
||||
$lang = auth_get_lang();
|
||||
$user = auth_current_user();
|
||||
$errors = [];
|
||||
$flashSuccess = '';
|
||||
$flashError = '';
|
||||
|
||||
$profileErrors = [];
|
||||
$profileSuccess = false;
|
||||
|
||||
// --- 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.';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (!auth_verify_csrf($_POST['_csrf'] ?? null)) {
|
||||
$flashError = 'Sicherheitsfehler. Bitte Formular erneut absenden.';
|
||||
} else {
|
||||
$fullName = $_POST['full_name'] ?? '';
|
||||
$preferredLang = $_POST['preferred_lang'] ?? $lang;
|
||||
$fullName = $_POST['full_name'] ?? ($user['full_name'] ?? '');
|
||||
$preferredLang = $_POST['preferred_lang'] ?? ($user['preferred_lang'] ?? $lang);
|
||||
|
||||
$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
|
||||
$lang = auth_get_lang(); // kann sich geändert haben
|
||||
} else {
|
||||
$profileErrors = $result['errors'];
|
||||
$errors = $result['errors'] ?? [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Avatar-Initialen ---
|
||||
$initials = auth_user_initials($user);
|
||||
$csrfToken = auth_csrf_token();
|
||||
$initials = auth_user_initials($user);
|
||||
$avatarUrl = auth_user_avatar_url($user);
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?php echo htmlspecialchars($lang, ENT_QUOTES); ?>">
|
||||
<html lang="<?php echo htmlspecialchars($lang, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<head>
|
||||
<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">
|
||||
|
||||
<!-- Fonts -->
|
||||
<!-- Fonts: Montserrat + Inter -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<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">
|
||||
|
||||
<!-- Main CSS -->
|
||||
<!-- Main stylesheet -->
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=1">
|
||||
</head>
|
||||
<body>
|
||||
<?php
|
||||
$langVar = $lang;
|
||||
include __DIR__ . '/partials/header.php';
|
||||
?>
|
||||
<?php include __DIR__ . '/partials/header.php'; ?>
|
||||
|
||||
<main class="page-main">
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="section-title" data-i18n="account_title">Mein Konto</h1>
|
||||
<p class="section-lead" data-i18n="account_intro">
|
||||
Verwalte deine Profildaten und behalte deine USB-Tests im Überblick.
|
||||
</p>
|
||||
<main class="account-page">
|
||||
<div class="container">
|
||||
<div class="account-grid">
|
||||
<!-- Profil -->
|
||||
<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>
|
||||
|
||||
<div class="account-layout">
|
||||
<!-- Sidebar: Avatar + Basisinfos -->
|
||||
<aside class="account-sidebar">
|
||||
<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 if ($flashSuccess): ?>
|
||||
<div class="auth-flash-success">
|
||||
<?php echo htmlspecialchars($flashSuccess, ENT_QUOTES, 'UTF-8'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($flashError): ?>
|
||||
<div class="auth-flash-error">
|
||||
<?php echo htmlspecialchars($flashError, ENT_QUOTES, 'UTF-8'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" novalidate>
|
||||
<input type="hidden" name="_csrf" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="email">E-Mail-Adresse</label>
|
||||
<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"
|
||||
id="full_name"
|
||||
name="full_name"
|
||||
required
|
||||
value="<?php echo htmlspecialchars($_POST['full_name'] ?? ($user['full_name'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>"
|
||||
>
|
||||
<?php if (!empty($errors['full_name'])): ?>
|
||||
<div class="form-error"><?php echo htmlspecialchars($errors['full_name'], ENT_QUOTES, 'UTF-8'); ?></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 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'] ?? ($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>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</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
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Änderungen speichern
|
||||
</button>
|
||||
<a href="/logout.php" class="auth-link">
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<!-- Main: Profilformular + Platzhalter für später -->
|
||||
<section class="account-main">
|
||||
<div class="card">
|
||||
<h2 data-i18n="account_profile_heading">Profil</h2>
|
||||
<!-- Avatar / Meta -->
|
||||
<section class="account-card">
|
||||
<h2 class="account-title">Avatar & Konto</h2>
|
||||
<p class="account-subtitle">
|
||||
Dein Avatar wird aktuell aus deinen Initialen erzeugt. Später kannst du hier ein eigenes Bild hochladen.
|
||||
</p>
|
||||
|
||||
<?php if (!empty($profileErrors['csrf'])): ?>
|
||||
<div class="alert alert-error">
|
||||
<?php echo htmlspecialchars($profileErrors['csrf'], ENT_QUOTES); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($profileSuccess): ?>
|
||||
<div class="alert alert-success" data-i18n="account_profile_updated">
|
||||
Profil wurde aktualisiert.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="/account.php?lang=<?php echo urlencode($lang); ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES); ?>">
|
||||
<input type="hidden" name="action" value="update_profile">
|
||||
|
||||
<div class="form-row">
|
||||
<label for="full_name" data-i18n="account_full_name_label">Vollständiger Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="full_name"
|
||||
name="full_name"
|
||||
required
|
||||
value="<?php echo htmlspecialchars($user['full_name'] ?? '', ENT_QUOTES); ?>"
|
||||
>
|
||||
<?php if (!empty($profileErrors['full_name'])): ?>
|
||||
<p class="form-error"><?php echo htmlspecialchars($profileErrors['full_name'], ENT_QUOTES); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="preferred_lang" data-i18n="account_lang_label">Bevorzugte Sprache</label>
|
||||
<select id="preferred_lang" name="preferred_lang">
|
||||
<?php
|
||||
$pl = $user['preferred_lang'] ?? $lang;
|
||||
?>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary" data-i18n="account_profile_save">
|
||||
Änderungen speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<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;">
|
||||
|
||||
</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 class="card card-muted">
|
||||
<h2 data-i18n="account_usb_heading">Deine USB-Sticks & Testergebnisse</h2>
|
||||
<p class="muted" data-i18n="account_usb_placeholder">
|
||||
Hier wirst du später eine Übersicht deiner registrierten USB-Sticks und Testergebnisse sehen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card card-muted">
|
||||
<h2 data-i18n="account_avatar_heading">Avatar</h2>
|
||||
<p class="muted" data-i18n="account_avatar_placeholder">
|
||||
Standardmäßig verwenden wir deine Initialen. Später kannst du hier ein eigenes Profilbild hochladen.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="account-meta" style="margin-top:1.5rem;">
|
||||
<p><strong>Account-ID:</strong> <?php echo (int)$user['id']; ?></p>
|
||||
<p><strong>Registriert am:</strong>
|
||||
<?php
|
||||
if (!empty($user['created_at'])) {
|
||||
echo htmlspecialchars($user['created_at'], ENT_QUOTES, 'UTF-8');
|
||||
} else {
|
||||
echo '–';
|
||||
}
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php include __DIR__ . '/partials/footer.php'; ?>
|
||||
|
||||
<script src="/assets/js/lang.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -553,3 +553,189 @@ body {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
(function () {
|
||||
const translations = {
|
||||
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_subtitle: "USB-Sticks auf Fakes testen",
|
||||
btn_login: "Login",
|
||||
@@ -118,6 +130,17 @@
|
||||
},
|
||||
|
||||
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_subtitle: "Test USB drives for fakes",
|
||||
btn_login: "Login",
|
||||
@@ -234,6 +257,17 @@
|
||||
|
||||
// Italienisch (kurz, sachlich)
|
||||
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_subtitle: "Controlla le chiavette USB contraffatte",
|
||||
btn_login: "Login",
|
||||
@@ -351,6 +385,17 @@
|
||||
|
||||
// Französisch (kurz, sachlich)
|
||||
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_subtitle: "Tester les clés USB contrefaites",
|
||||
btn_login: "Connexion",
|
||||
|
||||
147
public/index.php
147
public/index.php
@@ -1,124 +1,42 @@
|
||||
<?php
|
||||
// public/index.php
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<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" />
|
||||
// Sprachlogik:
|
||||
$lang = $_GET['lang'] ?? 'en';
|
||||
$lang = in_array($lang, ['de','en','it','fr']) ? $lang : 'en';
|
||||
|
||||
<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 -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<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">
|
||||
|
||||
<!-- Tailwind CDN (für Dev; später durch eigenes CSS ersetzbar) -->
|
||||
<!-- Tailwind (Dev) -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
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>
|
||||
|
||||
<!-- Language JS -->
|
||||
<script src="/assets/js/lang.js?v=1" defer></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-brand-bg text-brand-text font-sans antialiased scroll-smooth">
|
||||
|
||||
<!-- Page Wrapper -->
|
||||
<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 -->
|
||||
<div class="flex items-center gap-4 sm:gap-6">
|
||||
<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>
|
||||
<!-- HEADER -->
|
||||
<?php include __DIR__ . "/partials/header.php"; ?>
|
||||
|
||||
<!-- Language Switch -->
|
||||
<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 CONTENT -->
|
||||
<main class="flex-1">
|
||||
<!-- Hero -->
|
||||
<section id="hero" class="relative overflow-hidden">
|
||||
@@ -512,20 +430,11 @@
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<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 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>
|
||||
<!-- FOOTER -->
|
||||
<?php include __DIR__ . "/partials/footer.php"; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- I18n + Login/Avatar Logic -->
|
||||
<script src="/assets/js/lang.js?v=1"></script>
|
||||
|
||||
<script src="/assets/js/header.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
182
public/login.php
182
public/login.php
@@ -1,82 +1,146 @@
|
||||
<?php
|
||||
// public/login.php
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../config/db.php';
|
||||
require __DIR__ . '/../src/Auth.php';
|
||||
require __DIR__ . '/../src/auth.php'; // lädt auch config/db.php
|
||||
|
||||
$auth = new Auth($pdo);
|
||||
$error = '';
|
||||
$lang = auth_get_lang();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$identifier = trim($_POST['identifier'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
$errors = [];
|
||||
$globalError = '';
|
||||
|
||||
if ($auth->login($identifier, $password)) {
|
||||
header('Location: /'); // nach Login auf Startseite
|
||||
exit;
|
||||
} else {
|
||||
$error = 'Login fehlgeschlagen. Bitte Zugangsdaten prüfen.';
|
||||
// Optional: Redirect-Ziel (z. B. ?redirect=/account.php)
|
||||
$redirect = '/account.php';
|
||||
if (!empty($_GET['redirect']) && is_string($_GET['redirect'])) {
|
||||
// Nur interne Pfade erlauben, keine kompletten URLs
|
||||
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>
|
||||
<html lang="de">
|
||||
<html lang="<?php echo htmlspecialchars($lang, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Login – usbcheck.it</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Dein Tailwind CSS -->
|
||||
<link rel="stylesheet" href="/css/tailwind.css">
|
||||
|
||||
<!-- Fonts: Montserrat + Inter -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<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 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>
|
||||
<body>
|
||||
<?php include __DIR__ . '/partials/header.php'; ?>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="mb-4 text-sm text-[#E63946]">
|
||||
<?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<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>
|
||||
|
||||
<form method="post" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm mb-1" for="identifier">
|
||||
E-Mail oder Benutzername
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="identifier"
|
||||
name="identifier"
|
||||
required
|
||||
class="w-full border border-[#C8CBD0] rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#0051FF]"
|
||||
>
|
||||
</div>
|
||||
<?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>
|
||||
<?php endif; ?>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm mb-1" for="password">
|
||||
Passwort
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
required
|
||||
class="w-full border border-[#C8CBD0] rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#0051FF]"
|
||||
>
|
||||
</div>
|
||||
<?php if (!empty($errors['login'])): ?>
|
||||
<div style="margin-bottom: 1rem; padding: 0.75rem 1rem; border-radius: 12px; background: #ffe6e8; color: #a4001f; font-size: 0.9rem;">
|
||||
<?php echo htmlspecialchars($errors['login'], ENT_QUOTES, 'UTF-8'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full bg-[#0051FF] text-white font-[Montserrat] font-semibold rounded-lg py-2 mt-4 hover:bg-blue-700 transition"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
<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
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="identifier"
|
||||
name="identifier"
|
||||
required
|
||||
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 style="margin-bottom: 1rem;">
|
||||
<label for="password" style="display:block; font-size:0.9rem; margin-bottom:0.25rem;">
|
||||
Passwort
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
required
|
||||
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>
|
||||
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
|
||||
<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
|
||||
</button>
|
||||
|
||||
<a href="/register.php" class="btn btn-ghost" style="width: 100%; justify-content: center;">
|
||||
Noch kein Konto? Jetzt registrieren
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<?php include __DIR__ . '/partials/footer.php'; ?>
|
||||
|
||||
<script src="/assets/js/lang.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
13
public/partials/footer.php
Normal file
13
public/partials/footer.php
Normal 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>
|
||||
@@ -1,75 +1,62 @@
|
||||
<?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">
|
||||
<div class="container header-inner">
|
||||
<a href="<?php echo htmlspecialchars(usbcheck_url_with_lang('/', $currentLang)); ?>" class="logo-wrap">
|
||||
<img src="/img/logo.png" alt="usbcheck.it Logo" class="logo-img">
|
||||
</a>
|
||||
<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 -->
|
||||
<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">
|
||||
|
||||
<nav class="main-nav">
|
||||
<a href="<?php echo htmlspecialchars(usbcheck_url_with_lang('/', $currentLang)); ?>#how-it-works"
|
||||
class="nav-link"
|
||||
data-i18n="nav_how_it_works"></a>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<a href="<?php echo htmlspecialchars(usbcheck_url_with_lang('/', $currentLang)); ?>#features"
|
||||
class="nav-link"
|
||||
data-i18n="nav_features"></a>
|
||||
<!-- Navigation -->
|
||||
<div class="flex items-center gap-6">
|
||||
|
||||
<a href="<?php echo htmlspecialchars(usbcheck_url_with_lang('/', $currentLang)); ?>#pricing"
|
||||
class="nav-link"
|
||||
data-i18n="nav_pricing"></a>
|
||||
<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"></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="#security" class="hover:text-brand-primary transition-colors" data-i18n="nav_security"></a>
|
||||
<a href="#faq" class="hover:text-brand-primary transition-colors" data-i18n="nav_faq"></a>
|
||||
</nav>
|
||||
|
||||
<a href="<?php echo htmlspecialchars(usbcheck_url_with_lang('/', $currentLang)); ?>#faq"
|
||||
class="nav-link"
|
||||
data-i18n="nav_faq"></a>
|
||||
</nav>
|
||||
|
||||
<div class="header-actions">
|
||||
<div class="lang-switch" data-current-lang="<?php echo htmlspecialchars($currentLang); ?>">
|
||||
<button id="lang-current" class="lang-current">
|
||||
<?php echo strtoupper(htmlspecialchars($currentLang)); ?>
|
||||
<!-- Language Switch -->
|
||||
<div class="relative">
|
||||
<button id="langCurrent"
|
||||
class="text-xs uppercase tracking-[0.18em] text-brand-muted hover:text-brand-primary transition">
|
||||
<?= strtoupper($lang) ?>
|
||||
</button>
|
||||
<div id="lang-menu" class="lang-menu hidden">
|
||||
<?php foreach ($supportedLangs as $code => $label): ?>
|
||||
<button data-lang="<?php echo htmlspecialchars($code); ?>">
|
||||
<?php echo htmlspecialchars($label); ?>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div id="langMenu"
|
||||
class="hidden absolute right-0 mt-2 w-28 rounded-md bg-brand-surface border border-brand-border shadow-lg p-2 text-xs">
|
||||
<a href="?lang=de" class="block py-1 hover:text-brand-primary">Deutsch</a>
|
||||
<a href="?lang=en" class="block py-1 hover:text-brand-primary">English</a>
|
||||
<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>
|
||||
|
||||
<button id="login-button"
|
||||
class="btn btn-outline"
|
||||
data-i18n="btn_login"></button>
|
||||
<!-- 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-cyan-400 transition-colors"
|
||||
data-i18n="btn_login">
|
||||
</button>
|
||||
|
||||
<div id="user-avatar" class="user-avatar hidden" title="Account">
|
||||
<!-- Standard: Initialen, später durch Bild ersetzbar -->
|
||||
<span id="user-avatar-initials">U</span>
|
||||
</div>
|
||||
<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><?= strtoupper(substr($userInitials ?? 'U', 0, 2)) ?></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -1,42 +1,24 @@
|
||||
<?php
|
||||
// public/register.php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../src/auth.php';
|
||||
require __DIR__ . '/../src/auth.php';
|
||||
|
||||
$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 = [];
|
||||
$values = [
|
||||
'email' => '',
|
||||
'username' => '',
|
||||
'full_name' => '',
|
||||
'preferred_lang' => $lang,
|
||||
];
|
||||
$globalError = '';
|
||||
$result = null;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (!auth_verify_csrf($_POST['csrf_token'] ?? null)) {
|
||||
$errors['csrf'] = 'Deine Sitzung ist abgelaufen. Bitte Seite neu laden.';
|
||||
if (!auth_verify_csrf($_POST['_csrf'] ?? null)) {
|
||||
$globalError = 'Sicherheitsfehler. Bitte Formular erneut absenden.';
|
||||
} else {
|
||||
$email = $_POST['email'] ?? '';
|
||||
$username = $_POST['username'] ?? '';
|
||||
$fullName = $_POST['full_name'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
$passwordConfirm= $_POST['password_confirm'] ?? '';
|
||||
$preferredLang = $_POST['preferred_lang'] ?? $lang;
|
||||
|
||||
$values = [
|
||||
'email' => $email,
|
||||
'username' => $username,
|
||||
'full_name' => $fullName,
|
||||
'preferred_lang' => $preferredLang,
|
||||
];
|
||||
$email = $_POST['email'] ?? '';
|
||||
$username = $_POST['username'] ?? '';
|
||||
$fullName = $_POST['full_name'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
$passwordConfirm = $_POST['password_confirm'] ?? '';
|
||||
$preferredLang = $_POST['preferred_lang'] ?? $lang;
|
||||
|
||||
$result = auth_register_user(
|
||||
$email,
|
||||
@@ -47,152 +29,169 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$preferredLang
|
||||
);
|
||||
|
||||
if ($result['success']) {
|
||||
header('Location: /account.php?lang=' . urlencode($preferredLang));
|
||||
if ($result['success'] ?? false) {
|
||||
// Direkt nach Account-Seite
|
||||
header('Location: /account.php');
|
||||
exit;
|
||||
} else {
|
||||
$errors = array_merge($errors, $result['errors']);
|
||||
}
|
||||
|
||||
$errors = $result['errors'] ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
$csrfToken = auth_csrf_token();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?php echo htmlspecialchars($lang, ENT_QUOTES); ?>">
|
||||
<html lang="<?php echo htmlspecialchars($lang, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Registrierung – USBCheck</title>
|
||||
<title>Registrieren – usbcheck.it</title>
|
||||
<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.gstatic.com" crossorigin>
|
||||
<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">
|
||||
</head>
|
||||
<body>
|
||||
<?php
|
||||
// Header-Partial, erwartet ggf. $lang
|
||||
$langVar = $lang;
|
||||
include __DIR__ . '/partials/header.php';
|
||||
?>
|
||||
<?php include __DIR__ . '/partials/header.php'; ?>
|
||||
|
||||
<main class="page-main">
|
||||
<section class="section">
|
||||
<div class="container narrow">
|
||||
<h1 class="section-title" data-i18n="register_title">Konto erstellen</h1>
|
||||
<p class="section-lead" data-i18n="register_intro">
|
||||
Erstelle ein kostenloses Konto, um deine USB-Tests zu verwalten.
|
||||
</p>
|
||||
<main class="auth-page">
|
||||
<div class="container">
|
||||
<div class="auth-layout">
|
||||
<div class="auth-card">
|
||||
<h1 class="auth-title">Konto erstellen</h1>
|
||||
<p class="auth-subtitle">
|
||||
Erstelle einen kostenlosen Account, um deine USB-Tests zu speichern und mehrere Sticks zu verwalten.
|
||||
</p>
|
||||
|
||||
<?php if (!empty($errors['csrf'])): ?>
|
||||
<div class="alert alert-error">
|
||||
<?php echo htmlspecialchars($errors['csrf'], ENT_QUOTES); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($globalError): ?>
|
||||
<div class="auth-flash-error">
|
||||
<?php echo htmlspecialchars($globalError, ENT_QUOTES, 'UTF-8'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form class="form-card" method="post" action="/register.php?lang=<?php echo urlencode($lang); ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES); ?>">
|
||||
<form method="post" novalidate>
|
||||
<input type="hidden" name="_csrf" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
|
||||
<div class="form-row">
|
||||
<label for="email" data-i18n="register_email_label">E-Mail-Adresse</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
value="<?php echo htmlspecialchars($values['email'], ENT_QUOTES); ?>"
|
||||
>
|
||||
<?php if (!empty($errors['email'])): ?>
|
||||
<p class="form-error"><?php echo htmlspecialchars($errors['email'], ENT_QUOTES); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="username" data-i18n="register_username_label">Username</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
required
|
||||
value="<?php echo htmlspecialchars($values['username'], ENT_QUOTES); ?>"
|
||||
>
|
||||
<?php if (!empty($errors['username'])): ?>
|
||||
<p class="form-error"><?php echo htmlspecialchars($errors['username'], ENT_QUOTES); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="full_name" data-i18n="register_full_name_label">Vollständiger Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="full_name"
|
||||
name="full_name"
|
||||
required
|
||||
value="<?php echo htmlspecialchars($values['full_name'], ENT_QUOTES); ?>"
|
||||
>
|
||||
<?php if (!empty($errors['full_name'])): ?>
|
||||
<p class="form-error"><?php echo htmlspecialchars($errors['full_name'], ENT_QUOTES); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="form-row form-row-inline">
|
||||
<div>
|
||||
<label for="password" data-i18n="register_password_label">Passwort</label>
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="email">E-Mail-Adresse</label>
|
||||
<input
|
||||
class="form-input"
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
value="<?php echo htmlspecialchars($_POST['email'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||
>
|
||||
<?php if (!empty($errors['email'])): ?>
|
||||
<div class="form-error"><?php echo htmlspecialchars($errors['email'], ENT_QUOTES, 'UTF-8'); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="username">Benutzername</label>
|
||||
<input
|
||||
class="form-input"
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
required
|
||||
value="<?php echo htmlspecialchars($_POST['username'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||
>
|
||||
<?php if (!empty($errors['username'])): ?>
|
||||
<div class="form-error"><?php echo htmlspecialchars($errors['username'], ENT_QUOTES, 'UTF-8'); ?></div>
|
||||
<?php endif; ?>
|
||||
<div class="form-help">
|
||||
3–32 Zeichen, Buchstaben/Zahlen/._- erlaubt.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="full_name">Vollständiger Name</label>
|
||||
<input
|
||||
class="form-input"
|
||||
type="text"
|
||||
id="full_name"
|
||||
name="full_name"
|
||||
required
|
||||
value="<?php echo htmlspecialchars($_POST['full_name'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||
>
|
||||
<?php if (!empty($errors['full_name'])): ?>
|
||||
<div class="form-error"><?php echo htmlspecialchars($errors['full_name'], ENT_QUOTES, 'UTF-8'); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="password">Passwort</label>
|
||||
<input
|
||||
class="form-input"
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
required
|
||||
minlength="10"
|
||||
>
|
||||
<?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 class="form-help">
|
||||
Mindestens 10 Zeichen. Bitte ein sicheres Passwort wählen.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password_confirm" data-i18n="register_password_confirm_label">Passwort wiederholen</label>
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="password_confirm">Passwort wiederholen</label>
|
||||
<input
|
||||
class="form-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>
|
||||
<div class="form-error"><?php echo htmlspecialchars($errors['password_confirm'], ENT_QUOTES, 'UTF-8'); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="preferred_lang" data-i18n="register_lang_label">Bevorzugte Sprache</label>
|
||||
<select id="preferred_lang" name="preferred_lang">
|
||||
<option value="de" <?php echo $values['preferred_lang'] === 'de' ? 'selected' : ''; ?>>Deutsch</option>
|
||||
<option value="en" <?php echo $values['preferred_lang'] === 'en' ? 'selected' : ''; ?>>English</option>
|
||||
<option value="it" <?php echo $values['preferred_lang'] === 'it' ? 'selected' : ''; ?>>Italiano</option>
|
||||
<option value="fr" <?php echo $values['preferred_lang'] === 'fr' ? 'selected' : ''; ?>>Français</option>
|
||||
</select>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary" data-i18n="register_submit">
|
||||
Konto erstellen
|
||||
</button>
|
||||
<p class="form-hint">
|
||||
<span data-i18n="register_existing_hint">Du hast bereits ein Konto?</span>
|
||||
<a href="/login.php?lang=<?php echo urlencode($lang); ?>" data-i18n="register_existing_link">
|
||||
Zum Login
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Konto erstellen
|
||||
</button>
|
||||
<a class="auth-link" href="/login.php">
|
||||
Bereits ein Konto? Login
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php include __DIR__ . '/partials/footer.php'; ?>
|
||||
|
||||
<script src="/assets/js/lang.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
demo
|
||||
Reference in New Issue
Block a user