test
This commit is contained in:
parent
79755d2421
commit
603db35457
@ -71,7 +71,62 @@ class AuthController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout - Token löschen.
|
||||
* Web-Login mit Session (für Blade-Views).
|
||||
*/
|
||||
public function webLogin(Request $request)
|
||||
{
|
||||
$credentials = $request->validate([
|
||||
'email' => 'required|email',
|
||||
'password' => 'required|string',
|
||||
]);
|
||||
|
||||
if (Auth::attempt($credentials, $request->boolean('remember'))) {
|
||||
$request->session()->regenerate();
|
||||
return redirect()->intended('/');
|
||||
}
|
||||
|
||||
return back()->withErrors([
|
||||
'email' => 'Die eingegebenen Anmeldedaten sind ungültig.',
|
||||
])->onlyInput('email');
|
||||
}
|
||||
|
||||
/**
|
||||
* Web-Registrierung mit Session.
|
||||
*/
|
||||
public function webRegister(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users',
|
||||
'password' => 'required|string|min:8|confirmed',
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'name' => $validated['name'],
|
||||
'email' => $validated['email'],
|
||||
'password' => Hash::make($validated['password']),
|
||||
'role' => 'user',
|
||||
]);
|
||||
|
||||
Auth::login($user);
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Web-Logout (Session beenden).
|
||||
*/
|
||||
public function webLogout(Request $request)
|
||||
{
|
||||
Auth::logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout - Token löschen (API).
|
||||
*/
|
||||
public function logout(Request $request)
|
||||
{
|
||||
|
||||
@ -55,9 +55,10 @@ class EventWebController extends Controller
|
||||
$locations = Event::published()
|
||||
->whereNotNull('location_id')
|
||||
->with('location')
|
||||
->distinct()
|
||||
->get()
|
||||
->pluck('location.name')
|
||||
->pluck('location.city')
|
||||
->filter()
|
||||
->unique()
|
||||
->sort()
|
||||
->values();
|
||||
|
||||
|
||||
44
app/Http/Controllers/ProfileController.php
Normal file
44
app/Http/Controllers/ProfileController.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
public function show()
|
||||
{
|
||||
return view('profile', ['user' => Auth::user()]);
|
||||
}
|
||||
|
||||
public function updateInfo(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'email', 'max:255', 'unique:users,email,' . $user->id],
|
||||
]);
|
||||
|
||||
$user->update($validated);
|
||||
|
||||
return redirect()->route('profile')->with('success_info', 'Name und E‑Mail wurden gespeichert.');
|
||||
}
|
||||
|
||||
public function updatePassword(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'current_password' => ['required', 'current_password'],
|
||||
'password' => ['required', 'confirmed', Password::min(8)],
|
||||
]);
|
||||
|
||||
Auth::user()->update([
|
||||
'password' => Hash::make($request->password),
|
||||
]);
|
||||
|
||||
return redirect()->route('profile')->with('success_pw', 'Passwort wurde erfolgreich geändert.');
|
||||
}
|
||||
}
|
||||
@ -116,8 +116,7 @@ class Event extends Model
|
||||
public function scopeByLocation($query, $location)
|
||||
{
|
||||
return $query->whereHas('location', function ($q) use ($location) {
|
||||
$q->where('name', 'like', '%' . $location . '%')
|
||||
->orWhere('city', 'like', '%' . $location . '%');
|
||||
$q->where('city', $location);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,191 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>Login - {{ config('app.name', 'Veranstaltungen') }}</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<title>Anmelden – Veranstaltungs-App</title>
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=instrument-sans:400,500,600" rel="stylesheet" />
|
||||
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.bunny.net/css?family=instrument-sans:400,500,600,700" rel="stylesheet"/>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: 'Instrument Sans', sans-serif;
|
||||
background: linear-gradient(135deg, #f0f4ff 0%, #faf5ff 100%);
|
||||
min-height: 100vh;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
padding: 24px;
|
||||
}
|
||||
.card {
|
||||
background: white; border-radius: 16px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.10);
|
||||
padding: 40px 36px; width: 100%; max-width: 420px;
|
||||
}
|
||||
.logo-link {
|
||||
display: block; text-align: center;
|
||||
font-size: 1.3em; font-weight: 800; color: #1a1a2e;
|
||||
text-decoration: none; margin-bottom: 8px; letter-spacing: -0.02em;
|
||||
}
|
||||
.logo-link span { color: #667eea; }
|
||||
h1 { font-size: 1.5em; font-weight: 700; color: #1a1a2e; text-align: center; margin-bottom: 4px; }
|
||||
.subtitle { text-align: center; color: #9ca3af; font-size: 0.875em; margin-bottom: 28px; }
|
||||
.field { margin-bottom: 18px; }
|
||||
label { display: block; font-size: 0.85em; font-weight: 600; color: #374151; margin-bottom: 6px; }
|
||||
input[type="email"], input[type="password"] {
|
||||
width: 100%; padding: 10px 14px;
|
||||
border: 1.5px solid #e5e7eb; border-radius: 8px;
|
||||
font-size: 0.9em; color: #1a1a2e;
|
||||
transition: border-color 0.2s, box-shadow 0.2s; outline: none;
|
||||
}
|
||||
input:focus { border-color: #667eea; box-shadow: 0 0 0 3px rgba(102,126,234,0.15); }
|
||||
.row-between {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
margin-bottom: 22px; gap: 10px;
|
||||
}
|
||||
.remember-label { display: flex; align-items: center; gap: 6px; font-size: 0.82em; color: #6b7280; cursor: pointer; }
|
||||
.forgot-link { font-size: 0.82em; color: #667eea; text-decoration: none; font-weight: 600; }
|
||||
.forgot-link:hover { text-decoration: underline; }
|
||||
.btn-submit {
|
||||
width: 100%; padding: 11px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white; font-size: 0.95em; font-weight: 700;
|
||||
border: none; border-radius: 8px; cursor: pointer;
|
||||
transition: opacity 0.2s; margin-bottom: 16px;
|
||||
}
|
||||
.btn-submit:hover { opacity: 0.9; }
|
||||
.divider { border: none; border-top: 1px solid #f3f4f6; margin: 20px 0; }
|
||||
.register-hint { text-align: center; font-size: 0.85em; color: #6b7280; }
|
||||
.register-hint a { color: #667eea; font-weight: 600; text-decoration: none; }
|
||||
.register-hint a:hover { text-decoration: underline; }
|
||||
.demo-box {
|
||||
margin-top: 20px; background: #f0f4ff;
|
||||
border: 1px solid #c7d2fe; border-radius: 8px; padding: 12px 14px;
|
||||
}
|
||||
.demo-box p { font-size: 0.78em; color: #4338ca; font-weight: 600; margin-bottom: 6px; }
|
||||
.demo-box span { display: block; font-size: 0.78em; color: #4f46e5; margin-bottom: 2px; }
|
||||
.alert-error {
|
||||
background: #fef2f2; border: 1px solid #fecaca;
|
||||
border-radius: 8px; padding: 12px 14px; margin-bottom: 18px;
|
||||
}
|
||||
.alert-error p { font-size: 0.83em; color: #dc2626; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 min-h-screen flex items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||
<div class="w-full max-w-md">
|
||||
<!-- Logo / Header -->
|
||||
<div class="text-center mb-8">
|
||||
<a href="{{ url('/') }}" class="inline-flex items-center gap-2">
|
||||
<div class="w-12 h-12 bg-blue-600 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
<h1 class="mt-6 text-3xl font-bold text-gray-900 dark:text-white">Anmelden</h1>
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
Melden Sie sich in Ihrem Konto an, um fortzufahren
|
||||
</p>
|
||||
</div>
|
||||
<body>
|
||||
<div class="card">
|
||||
<a href="{{ url('/') }}" class="logo-link">Veranstaltungs<span>-App</span></a>
|
||||
<h1>Willkommen zurück</h1>
|
||||
<p class="subtitle">Melden Sie sich an, um fortzufahren</p>
|
||||
|
||||
<!-- Form -->
|
||||
<div class="bg-white dark:bg-gray-800 shadow-lg rounded-lg p-8">
|
||||
<form id="loginForm" class="space-y-6" @submit.prevent="handleLogin">
|
||||
<!-- Email Field -->
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
E-Mail Adresse
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="name@example.com"
|
||||
required
|
||||
autocomplete="email"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-white transition"
|
||||
/>
|
||||
<span class="error-message text-red-600 dark:text-red-400 text-sm mt-1 hidden"></span>
|
||||
</div>
|
||||
|
||||
<!-- Password Field -->
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Passwort
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Ihr Passwort"
|
||||
required
|
||||
autocomplete="current-password"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-white transition"
|
||||
/>
|
||||
<span class="error-message text-red-600 dark:text-red-400 text-sm mt-1 hidden"></span>
|
||||
</div>
|
||||
|
||||
<!-- Remember Me & Forgot Password -->
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="flex items-center gap-2">
|
||||
<input type="checkbox" name="remember" class="w-4 h-4 border border-gray-300 dark:border-gray-600 rounded accent-blue-600">
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">Anmeldedaten speichern</span>
|
||||
</label>
|
||||
<a href="{{ url('/forgot-password') }}" class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 font-medium">
|
||||
Passwort vergessen?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-800 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 flex items-center justify-center gap-2"
|
||||
>
|
||||
<span class="loading hidden">
|
||||
<svg class="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="text">Anmelden</span>
|
||||
</button>
|
||||
|
||||
<!-- Success/Error Message -->
|
||||
<div id="messageContainer"></div>
|
||||
</form>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="mt-8 border-t border-gray-300 dark:border-gray-700"></div>
|
||||
|
||||
<!-- Register Link -->
|
||||
<div class="mt-6 text-center">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Noch kein Konto?
|
||||
<a href="{{ url('/register') }}" class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 font-semibold">
|
||||
Jetzt registrieren
|
||||
</a>
|
||||
</p>
|
||||
@if ($errors->any())
|
||||
<div class="alert-error">
|
||||
@foreach ($errors->all() as $error)
|
||||
<p>{{ $error }}</p>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Demo Users Info -->
|
||||
<div class="mt-6 p-3 bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-800 rounded-lg">
|
||||
<p class="text-xs font-semibold text-blue-900 dark:text-blue-200 mb-2">Demo Konten:</p>
|
||||
<div class="space-y-1 text-xs text-blue-800 dark:text-blue-300">
|
||||
<p>👤 user@example.com / password123</p>
|
||||
<p>🎭 organizer@example.com / password123</p>
|
||||
<p>👨💼 admin@example.com / password123</p>
|
||||
</div>
|
||||
<form method="POST" action="{{ route('login.post') }}">
|
||||
@csrf
|
||||
<div class="field">
|
||||
<label for="email">E-Mail Adresse</label>
|
||||
<input type="email" id="email" name="email"
|
||||
value="{{ old('email') }}"
|
||||
placeholder="name@example.com"
|
||||
required autocomplete="email">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="password">Passwort</label>
|
||||
<input type="password" id="password" name="password"
|
||||
placeholder="Ihr Passwort"
|
||||
required autocomplete="current-password">
|
||||
</div>
|
||||
<div class="row-between">
|
||||
<label class="remember-label">
|
||||
<input type="checkbox" name="remember" {{ old('remember') ? 'checked' : '' }}>
|
||||
Angemeldet bleiben
|
||||
</label>
|
||||
<a href="{{ route('forgot-password') }}" class="forgot-link">Passwort vergessen?</a>
|
||||
</div>
|
||||
<button type="submit" class="btn-submit">Anmelden</button>
|
||||
</form>
|
||||
|
||||
<hr class="divider">
|
||||
<p class="register-hint">
|
||||
Noch kein Konto? <a href="{{ route('register') }}">Jetzt registrieren</a>
|
||||
</p>
|
||||
<div class="demo-box">
|
||||
<p>Demo-Konten:</p>
|
||||
<span>👤 user@example.com / password123</span>
|
||||
<span>🎭 organizer@example.com / password123</span>
|
||||
<span>👨💼 admin@example.com / password123</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const email = document.getElementById('email').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const btn = e.target.querySelector('button[type="submit"]');
|
||||
const loadingSpan = btn.querySelector('.loading');
|
||||
const textSpan = btn.querySelector('.text');
|
||||
|
||||
// Show loading state
|
||||
loadingSpan.classList.remove('hidden');
|
||||
textSpan.textContent = 'Wird angemeldet...';
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.success) {
|
||||
// Store token
|
||||
localStorage.setItem('auth_token', data.token);
|
||||
localStorage.setItem('user', JSON.stringify(data.user));
|
||||
|
||||
// Show success message
|
||||
showMessage('Erfolgreich angemeldet! Sie werden weitergeleitet...', 'success');
|
||||
|
||||
// Redirect after 1.5 seconds
|
||||
setTimeout(() => {
|
||||
window.location.href = '/';
|
||||
}, 1500);
|
||||
} else {
|
||||
const errorMsg = data.message || 'Anmeldung fehlgeschlagen';
|
||||
showMessage(errorMsg, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage('Fehler bei der Anmeldung. Bitte versuchen Sie es später erneut.', 'error');
|
||||
console.error('Login error:', error);
|
||||
} finally {
|
||||
loadingSpan.classList.add('hidden');
|
||||
textSpan.textContent = 'Anmelden';
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
function showMessage(message, type) {
|
||||
const container = document.getElementById('messageContainer');
|
||||
const bgColor = type === 'success' ? 'bg-green-50 dark:bg-green-900/30 border-green-200 dark:border-green-800' : 'bg-red-50 dark:bg-red-900/30 border-red-200 dark:border-red-800';
|
||||
const textColor = type === 'success' ? 'text-green-800 dark:text-green-300' : 'text-red-800 dark:text-red-300';
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="p-4 ${bgColor} border rounded-lg">
|
||||
<p class="${textColor} text-sm font-medium">${message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -3,328 +3,155 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ $event->title }} - Veranstaltungen</title>
|
||||
<title>{{ $event->title }} - Veranstaltungs-App</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
margin-bottom: 30px;
|
||||
font-weight: 600;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.back-link:hover {
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
.detail-card {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.2);
|
||||
}
|
||||
.detail-image {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 8em;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.detail-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.detail-content {
|
||||
padding: 50px;
|
||||
}
|
||||
.event-category {
|
||||
display: inline-block;
|
||||
background: #f0f0f0;
|
||||
color: #667eea;
|
||||
padding: 8px 20px;
|
||||
border-radius: 25px;
|
||||
font-size: 0.9em;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.event-title {
|
||||
font-size: 2.5em;
|
||||
font-weight: 800;
|
||||
color: #333;
|
||||
margin-bottom: 30px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.event-source {
|
||||
display: inline-block;
|
||||
background: #e8f0ff;
|
||||
color: #667eea;
|
||||
padding: 8px 15px;
|
||||
border-radius: 5px;
|
||||
font-size: 0.85em;
|
||||
margin-bottom: 30px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 30px;
|
||||
margin-bottom: 40px;
|
||||
padding-bottom: 40px;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
}
|
||||
.info-box {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
.info-icon {
|
||||
font-size: 2em;
|
||||
min-width: 40px;
|
||||
}
|
||||
.info-text h4 {
|
||||
font-size: 0.85em;
|
||||
text-transform: uppercase;
|
||||
color: #999;
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.info-text p {
|
||||
font-size: 1.1em;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 1.5em;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 3px solid #667eea;
|
||||
}
|
||||
.description {
|
||||
color: #555;
|
||||
font-size: 1.05em;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.occurrences {
|
||||
background: #f8f9ff;
|
||||
padding: 25px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.occurrence-item {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
border-left: 4px solid #667eea;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.occurrence-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.occurrence-date {
|
||||
font-weight: 700;
|
||||
font-size: 1.1em;
|
||||
color: #333;
|
||||
}
|
||||
.occurrence-time {
|
||||
color: #999;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.occurrence-status {
|
||||
display: inline-block;
|
||||
background: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.85em;
|
||||
font-weight: 600;
|
||||
}
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 50px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.btn {
|
||||
padding: 15px 40px;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 1em;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
.btn-secondary {
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
border: 2px solid #e0e0e0;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background: #e8e8e8;
|
||||
}
|
||||
.contact-info {
|
||||
background: #f8f9ff;
|
||||
padding: 25px;
|
||||
border-radius: 10px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
.contact-item {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
.contact-item strong {
|
||||
color: #333;
|
||||
}
|
||||
.contact-item a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
}
|
||||
.contact-item a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f9f9f9; color: #1a1a2e; }
|
||||
main { max-width: 900px; margin: 0 auto; padding: 40px 20px; }
|
||||
|
||||
.back-link { display: inline-flex; align-items: center; gap: 6px; color: #667eea; text-decoration: none; font-size: 0.875em; font-weight: 600; margin-bottom: 24px; transition: gap 0.2s; }
|
||||
.back-link:hover { gap: 10px; }
|
||||
|
||||
.detail-card { background: white; border-radius: 16px; overflow: hidden; box-shadow: 0 2px 12px rgba(0,0,0,0.08); border: 1px solid #eee; }
|
||||
|
||||
.detail-stripe { height: 6px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
|
||||
|
||||
.detail-content { padding: 40px; }
|
||||
|
||||
.meta-top { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; flex-wrap: wrap; }
|
||||
.category-badge { padding: 4px 12px; border-radius: 20px; font-size: 0.75em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; background: #f0f0ff; color: #4338ca; }
|
||||
.source-badge { padding: 4px 12px; border-radius: 20px; font-size: 0.75em; font-weight: 600; background: #f0fdf4; color: #15803d; }
|
||||
|
||||
h1 { font-size: 2.2em; font-weight: 800; line-height: 1.2; margin-bottom: 32px; color: #1a1a2e; }
|
||||
|
||||
.info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 36px; padding-bottom: 36px; border-bottom: 1px solid #f3f4f6; }
|
||||
.info-box { background: #f9f9f9; border-radius: 10px; padding: 16px 20px; display: flex; gap: 14px; align-items: flex-start; }
|
||||
.info-icon { font-size: 1.5em; line-height: 1; }
|
||||
.info-text label { display: block; font-size: 0.72em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #9ca3af; margin-bottom: 4px; }
|
||||
.info-text p { font-size: 0.95em; font-weight: 600; color: #1a1a2e; line-height: 1.4; }
|
||||
|
||||
.section-title { font-size: 1.15em; font-weight: 700; color: #1a1a2e; margin-bottom: 16px; padding-bottom: 10px; border-bottom: 2px solid #667eea; display: inline-block; }
|
||||
|
||||
.description { color: #4b5563; font-size: 1em; line-height: 1.8; margin-bottom: 40px; }
|
||||
|
||||
.occurrences { display: flex; flex-direction: column; gap: 10px; margin-bottom: 40px; }
|
||||
.occurrence-item { background: #f9f9f9; border-left: 3px solid #667eea; border-radius: 0 8px 8px 0; padding: 14px 20px; display: flex; justify-content: space-between; align-items: center; gap: 16px; }
|
||||
.occurrence-date { font-weight: 700; font-size: 0.95em; }
|
||||
.occurrence-time { color: #6b7280; font-size: 0.85em; margin-top: 2px; }
|
||||
.occurrence-status { font-size: 0.75em; font-weight: 700; background: #ecfdf5; color: #15803d; padding: 3px 10px; border-radius: 20px; white-space: nowrap; }
|
||||
|
||||
.contact-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 40px; }
|
||||
.contact-item { background: #f9f9f9; border-radius: 10px; padding: 16px 20px; }
|
||||
.contact-item label { display: block; font-size: 0.72em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #9ca3af; margin-bottom: 6px; }
|
||||
.contact-item a, .contact-item span { font-size: 0.9em; color: #667eea; font-weight: 600; text-decoration: none; }
|
||||
.contact-item a:hover { text-decoration: underline; }
|
||||
|
||||
.action-buttons { display: flex; gap: 12px; margin-top: 40px; flex-wrap: wrap; }
|
||||
.btn-primary { padding: 12px 28px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 8px; text-decoration: none; font-weight: 700; font-size: 0.9em; transition: opacity 0.2s; }
|
||||
.btn-primary:hover { opacity: 0.9; }
|
||||
.btn-secondary { padding: 12px 28px; background: #f3f4f6; color: #374151; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 0.9em; transition: background 0.2s; }
|
||||
.btn-secondary:hover { background: #e5e7eb; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a href="/" class="back-link">← Zurück zur Übersicht</a>
|
||||
@include('partials.header')
|
||||
|
||||
<main>
|
||||
<a href="{{ route('events') }}" class="back-link">← Zurück zur Übersicht</a>
|
||||
|
||||
<div class="detail-card">
|
||||
<div class="detail-image">
|
||||
@if($event->image_url)
|
||||
<img src="{{ $event->image_url }}" alt="{{ $event->title }}">
|
||||
@else
|
||||
<span>🎭</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="detail-stripe"></div>
|
||||
<div class="detail-content">
|
||||
@if($event->category)
|
||||
<span class="event-category">{{ $event->category }}</span>
|
||||
@endif
|
||||
@if($event->source)
|
||||
<span class="event-source">Quelle: {{ $event->source->name }}</span>
|
||||
@endif
|
||||
|
||||
<h1 class="event-title">{{ $event->title }}</h1>
|
||||
<div class="meta-top">
|
||||
@if($event->category)
|
||||
<span class="category-badge">{{ $event->category }}</span>
|
||||
@endif
|
||||
@if($event->source)
|
||||
<span class="source-badge">Quelle: {{ $event->source->name }}</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<h1>{{ $event->title }}</h1>
|
||||
|
||||
<div class="info-grid">
|
||||
@if($event->occurrences->count() > 0)
|
||||
@php $firstOccurrence = $event->occurrences->first(); @endphp
|
||||
@php $first = $event->occurrences->first(); @endphp
|
||||
<div class="info-box">
|
||||
<div class="info-icon">📅</div>
|
||||
<span class="info-icon">📅</span>
|
||||
<div class="info-text">
|
||||
<h4>Nächster Termin</h4>
|
||||
<p>{{ $firstOccurrence->start_datetime->format('d. F Y') }}</p>
|
||||
<label>Nächster Termin</label>
|
||||
<p>{{ $first->start_datetime->format('d. F Y') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-box">
|
||||
<div class="info-icon">⏰</div>
|
||||
<span class="info-icon">⏰</span>
|
||||
<div class="info-text">
|
||||
<h4>Uhrzeit</h4>
|
||||
<p>{{ $firstOccurrence->start_datetime->format('H:i') }}
|
||||
@if($firstOccurrence->end_datetime)
|
||||
- {{ $firstOccurrence->end_datetime->format('H:i') }}
|
||||
@endif
|
||||
Uhr
|
||||
</p>
|
||||
<label>Uhrzeit</label>
|
||||
<p>{{ $first->start_datetime->format('H:i') }}{{ $first->end_datetime ? ' – ' . $first->end_datetime->format('H:i') : '' }} Uhr</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@if($event->location)
|
||||
<div class="info-box">
|
||||
<div class="info-icon">📍</div>
|
||||
<span class="info-icon">📍</span>
|
||||
<div class="info-text">
|
||||
<h4>Ort</h4>
|
||||
<p>{{ $event->location->name }}<br>{{ $event->location->city }}</p>
|
||||
<label>Ort</label>
|
||||
<p>{{ $event->location->name }}, {{ $event->location->city }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if($event->description)
|
||||
<h2 class="section-title">Beschreibung</h2>
|
||||
<div class="section-title">Beschreibung</div>
|
||||
<p class="description">{{ $event->description }}</p>
|
||||
@endif
|
||||
|
||||
@if($event->occurrences->count() > 0)
|
||||
<h2 class="section-title">Alle Termine ({{ $event->occurrences->count() }})</h2>
|
||||
<div class="section-title">Alle Termine ({{ $event->occurrences->count() }})</div>
|
||||
<div class="occurrences">
|
||||
@foreach($event->occurrences as $occurrence)
|
||||
@foreach($event->occurrences as $occ)
|
||||
<div class="occurrence-item">
|
||||
<div>
|
||||
<div class="occurrence-date">
|
||||
📅 {{ $occurrence->start_datetime->format('d. F Y') }}
|
||||
</div>
|
||||
<div class="occurrence-time">
|
||||
⏰ {{ $occurrence->start_datetime->format('H:i') }}
|
||||
@if($occurrence->end_datetime)
|
||||
- {{ $occurrence->end_datetime->format('H:i') }}
|
||||
@endif
|
||||
Uhr
|
||||
</div>
|
||||
<div class="occurrence-date">{{ $occ->start_datetime->format('d. F Y') }}</div>
|
||||
<div class="occurrence-time">{{ $occ->start_datetime->format('H:i') }}{{ $occ->end_datetime ? ' – ' . $occ->end_datetime->format('H:i') : '' }} Uhr</div>
|
||||
</div>
|
||||
<span class="occurrence-status">{{ ucfirst($occurrence->status) }}</span>
|
||||
@php
|
||||
$statusLabels = [
|
||||
'scheduled' => 'Geplant',
|
||||
'cancelled' => 'Abgesagt',
|
||||
'postponed' => 'Verschoben',
|
||||
'sold_out' => 'Ausverkauft',
|
||||
'completed' => 'Abgeschlossen',
|
||||
];
|
||||
$statusLabel = $statusLabels[$occ->status] ?? ucfirst($occ->status);
|
||||
@endphp
|
||||
<span class="occurrence-status">{{ $statusLabel }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($event->contact_email || $event->contact_phone || $event->website_url)
|
||||
<h2 class="section-title">Kontakt & Links</h2>
|
||||
<div class="contact-info">
|
||||
<div class="section-title">Kontakt & Links</div>
|
||||
<div class="contact-grid">
|
||||
@if($event->contact_email)
|
||||
<div class="contact-item">
|
||||
<strong>✉️ Email:</strong>
|
||||
<label>✉️ E-Mail</label>
|
||||
<a href="mailto:{{ $event->contact_email }}">{{ $event->contact_email }}</a>
|
||||
</div>
|
||||
@endif
|
||||
@if($event->contact_phone)
|
||||
<div class="contact-item">
|
||||
<strong>📞 Telefon:</strong>
|
||||
<label>📞 Telefon</label>
|
||||
<a href="tel:{{ $event->contact_phone }}">{{ $event->contact_phone }}</a>
|
||||
</div>
|
||||
@endif
|
||||
@if($event->website_url)
|
||||
<div class="contact-item">
|
||||
<strong>🌐 Website:</strong>
|
||||
<a href="{{ $event->website_url }}" target="_blank" rel="noopener">Zur Website</a>
|
||||
<label>🌐 Website</label>
|
||||
<a href="{{ $event->website_url }}" target="_blank" rel="noopener">Zur Website →</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@ -332,12 +159,15 @@
|
||||
|
||||
<div class="action-buttons">
|
||||
@if($event->website_url)
|
||||
<a href="{{ $event->website_url }}" class="btn btn-primary" target="_blank">🎫 Jetzt Tickets buchen</a>
|
||||
<a href="{{ $event->website_url }}" class="btn-primary" target="_blank">🎫 Tickets / Mehr Info</a>
|
||||
@endif
|
||||
<a href="/" class="btn btn-secondary">Mehr Veranstaltungen entdecken</a>
|
||||
<a href="{{ route('events') }}" class="btn-secondary">← Alle Veranstaltungen</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@include('partials.footer')
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -108,7 +108,7 @@
|
||||
<span>📍 {{ $event->location->name }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<a href="{{ route('events.show', $event) }}" class="event-link">Details ansehen →</a>
|
||||
<a href="{{ route('event.detail', $event) }}" class="event-link">Details ansehen →</a>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
20
resources/views/legal/agb.blade.php
Normal file
20
resources/views/legal/agb.blade.php
Normal file
@ -0,0 +1,20 @@
|
||||
@include('partials.header')
|
||||
|
||||
<main style="max-width:1100px;margin:40px auto;padding:0 20px;">
|
||||
<h1 style="font-size:2rem;margin-bottom:16px;">Allgemeine Geschäftsbedingungen (AGB)</h1>
|
||||
|
||||
<section style="background:#fff;padding:20px;border-radius:8px;box-shadow:0 1px 6px rgba(0,0,0,0.04);">
|
||||
<h2 style="margin-top:0;">1. Geltungsbereich</h2>
|
||||
<p>Diese AGB regeln die Nutzung der Plattform Veranstaltungs‑App zwischen Veranstaltungs‑App (Anbieter) und den Nutzern.</p>
|
||||
|
||||
<h2 style="margin-top:12px;">2. Leistungen</h2>
|
||||
<p>Der Anbieter stellt eine Plattform zur Suche, Anzeige und Verwaltung von Veranstaltungen zur Verfügung.</p>
|
||||
|
||||
<h2 style="margin-top:12px;">3. Haftung</h2>
|
||||
<p>Der Anbieter haftet nur bei Vorsatz und grober Fahrlässigkeit. Für Inhalte Dritter übernimmt der Anbieter keine Haftung.</p>
|
||||
|
||||
<p style="margin-top:20px;color:#6b7280;font-size:0.9em;">Dies ist eine Muster‑AGB. Bitte lassen Sie die AGB rechtlich prüfen, bevor Sie diese veröffentlichen.</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@include('partials.footer')
|
||||
23
resources/views/legal/impressum.blade.php
Normal file
23
resources/views/legal/impressum.blade.php
Normal file
@ -0,0 +1,23 @@
|
||||
@include('partials.header')
|
||||
|
||||
<main style="max-width:1100px;margin:40px auto;padding:0 20px;">
|
||||
<h1 style="font-size:2rem;margin-bottom:16px;">Impressum</h1>
|
||||
|
||||
<section style="background:#fff;padding:20px;border-radius:8px;box-shadow:0 1px 6px rgba(0,0,0,0.04);">
|
||||
<p><strong>Angaben gemäß § 5 TMG:</strong></p>
|
||||
<p>Veranstaltungs‑App<br>
|
||||
Musterstraße 1<br>
|
||||
12345 Musterstadt</p>
|
||||
|
||||
<p><strong>Kontakt:</strong><br>
|
||||
Telefon: +49 (0)123 456789<br>
|
||||
E‑Mail: support@veranstaltungen.app</p>
|
||||
|
||||
<h2 style="margin-top:16px;">Vertretungsberechtigte(r)</h2>
|
||||
<p>Max Mustermann</p>
|
||||
|
||||
<p style="margin-top:20px;color:#6b7280;font-size:0.9em;">Hinweis: Dieses Impressum ist ein Platzhalter. Prüfen Sie bitte die rechtlichen Anforderungen und ergänzen Sie ggf. Angaben wie USt‑ID, Handelsregister etc.</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@include('partials.footer')
|
||||
25
resources/views/legal/privacy.blade.php
Normal file
25
resources/views/legal/privacy.blade.php
Normal file
@ -0,0 +1,25 @@
|
||||
@include('partials.header')
|
||||
|
||||
<main style="max-width:1100px;margin:40px auto;padding:0 20px;">
|
||||
<h1 style="font-size:2rem;margin-bottom:16px;">Datenschutzerklärung</h1>
|
||||
|
||||
<section style="background:#fff;padding:20px;border-radius:8px;box-shadow:0 1px 6px rgba(0,0,0,0.04);">
|
||||
<p>Diese Datenschutzerklärung informiert Sie über die Verarbeitung personenbezogener Daten im Zusammenhang mit der Nutzung der Veranstaltungs‑App.</p>
|
||||
|
||||
<h2 style="margin-top:16px;">1. Verantwortlicher</h2>
|
||||
<p>Verantwortlich: Veranstaltungs‑App, Musterstraße 1, 12345 Musterstadt, E‑Mail: support@veranstaltungen.app</p>
|
||||
|
||||
<h2 style="margin-top:16px;">2. Erhobene Daten</h2>
|
||||
<p>Wir verarbeiten u. a. Name, E‑Mail, Nutzungsdaten und eventbezogene Informationen zur Bereitstellung der Dienste.</p>
|
||||
|
||||
<h2 style="margin-top:16px;">3. Zwecke & Rechtsgrundlagen</h2>
|
||||
<p>Die Datenverarbeitung dient der Vertragserfüllung, Sicherheitszwecken und der Verbesserung unseres Dienstes. Rechtsgrundlagen sind Art.6 DSGVO.</p>
|
||||
|
||||
<h2 style="margin-top:16px;">4. Ihre Rechte</h2>
|
||||
<p>Sie haben das Recht auf Auskunft, Berichtigung, Löschung, Datenübertragbarkeit und Widerspruch. Kontaktieren Sie uns unter support@veranstaltungen.app.</p>
|
||||
|
||||
<p style="margin-top:20px;color:#6b7280;font-size:0.9em;">Diese Seite ist ein Musterdokument. Bitte passen Sie den Text an die rechtlichen Anforderungen Ihres Projekts an.</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@include('partials.footer')
|
||||
@ -1,46 +1,126 @@
|
||||
<footer class="bg-gray-900 text-gray-300 mt-12">
|
||||
<div class="max-w-7xl mx-auto px-4 py-12">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 mb-8">
|
||||
<style>
|
||||
.site-footer {
|
||||
background: #1a1a2e;
|
||||
color: #9ca3af;
|
||||
margin-top: 80px;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
.site-footer .inner {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 56px 20px 32px;
|
||||
}
|
||||
.site-footer .grid {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1fr 1fr;
|
||||
gap: 48px;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.site-footer .grid { grid-template-columns: 1fr 1fr; gap: 32px; }
|
||||
}
|
||||
.site-footer .brand-name {
|
||||
font-size: 1.1em;
|
||||
font-weight: 800;
|
||||
color: white;
|
||||
letter-spacing: -0.02em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.site-footer .brand-name span { color: #818cf8; }
|
||||
.site-footer .brand-desc {
|
||||
line-height: 1.65;
|
||||
color: #6b7280;
|
||||
}
|
||||
.site-footer h5 {
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
font-size: 0.8em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.site-footer ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.site-footer ul a {
|
||||
color: #9ca3af;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.site-footer ul a:hover { color: white; }
|
||||
.site-footer .divider {
|
||||
border: none;
|
||||
border-top: 1px solid #2d2d44;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.site-footer .bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
.site-footer .copyright { color: #6b7280; }
|
||||
.site-footer .social {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
.site-footer .social a {
|
||||
color: #6b7280;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.site-footer .social a:hover { color: white; }
|
||||
</style>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="inner">
|
||||
<div class="grid">
|
||||
<div>
|
||||
<h4 class="text-white font-bold mb-4">Veranstaltungs-App</h4>
|
||||
<p class="text-sm">Die beste Plattform zur Entdeckung und Verwaltung von Veranstaltungen in Deiner Nähe.</p>
|
||||
<div class="brand-name">Veranstaltungs<span>-App</span></div>
|
||||
<p class="brand-desc">Die beste Plattform zur Entdeckung und Verwaltung von Veranstaltungen in Deiner Nähe.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-white font-bold mb-4">Navigation</h4>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li><a href="{{ url('/') }}" class="hover:text-white transition">Home</a></li>
|
||||
<li><a href="{{ route('events') }}" class="hover:text-white transition">Events</a></li>
|
||||
<h5>Navigation</h5>
|
||||
<ul>
|
||||
<li><a href="{{ url('/') }}">Home</a></li>
|
||||
<li><a href="{{ route('events') }}">Events</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-white font-bold mb-4">Rechtliches</h4>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li><a href="#" class="hover:text-white transition">Datenschutzerklärung</a></li>
|
||||
<li><a href="#" class="hover:text-white transition">Impressum</a></li>
|
||||
<li><a href="#" class="hover:text-white transition">AGB</a></li>
|
||||
<h5>Rechtliches</h5>
|
||||
<ul>
|
||||
<li><a href="{{ route('privacy') }}">Datenschutz</a></li>
|
||||
<li><a href="{{ route('impressum') }}">Impressum</a></li>
|
||||
<li><a href="{{ route('agb') }}">AGB</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-white font-bold mb-4">Kontakt</h4>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li>Email: <a href="mailto:support@veranstaltungen.app" class="text-blue-400 hover:text-blue-300">support@veranstaltungen.app</a></li>
|
||||
<li>Telefon: +49 (0)123 456789</li>
|
||||
<li>Adresse: Musterstraße 1, 12345 Musterstadt</li>
|
||||
<h5>Kontakt</h5>
|
||||
<ul>
|
||||
<li><a href="mailto:support@veranstaltungen.app">support@veranstaltungen.app</a></li>
|
||||
<li>+49 (0)123 456789</li>
|
||||
<li>Musterstraße 1, 12345 Musterstadt</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-gray-700 my-8"></div>
|
||||
<hr class="divider">
|
||||
|
||||
<div class="flex flex-col md:flex-row items-center justify-between">
|
||||
<div class="text-sm text-gray-400 mb-4 md:mb-0">© {{ date('Y') }} Veranstaltungs-App. Alle Rechte vorbehalten.</div>
|
||||
<div class="flex gap-6">
|
||||
<a href="https://facebook.com" class="text-gray-400 hover:text-white transition" target="_blank">Facebook</a>
|
||||
<a href="https://twitter.com" class="text-gray-400 hover:text-white transition" target="_blank">Twitter</a>
|
||||
<a href="https://instagram.com" class="text-gray-400 hover:text-white transition" target="_blank">Instagram</a>
|
||||
<div class="bottom">
|
||||
<span class="copyright">© {{ date('Y') }} Veranstaltungs-App. Alle Rechte vorbehalten.</span>
|
||||
<div class="social">
|
||||
<a href="https://facebook.com" target="_blank">Facebook</a>
|
||||
<a href="https://twitter.com" target="_blank">Twitter</a>
|
||||
<a href="https://instagram.com" target="_blank">Instagram</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,33 +1,162 @@
|
||||
<header class="bg-white dark:bg-gray-800 shadow-md mb-6">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="{{ url('/') }}" class="flex items-center gap-3">
|
||||
<span class="text-xl font-bold text-gray-900 dark:text-white">Veranstaltungs-App</span>
|
||||
</a>
|
||||
</div>
|
||||
<style>
|
||||
.site-header {
|
||||
background: white;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
box-shadow: 0 1px 6px rgba(0,0,0,0.06);
|
||||
}
|
||||
.site-header .inner {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.site-header .logo {
|
||||
font-size: 1.2em;
|
||||
font-weight: 800;
|
||||
color: #1a1a2e;
|
||||
text-decoration: none;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.site-header .logo span { color: #667eea; }
|
||||
.site-header nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 28px;
|
||||
}
|
||||
.site-header nav a {
|
||||
color: #6b7280;
|
||||
text-decoration: none;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.site-header nav a:hover { color: #667eea; }
|
||||
|
||||
<nav class="hidden md:flex items-center gap-6">
|
||||
<a href="{{ url('/') }}" class="text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition">Home</a>
|
||||
<a href="{{ route('events') }}" class="text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition">Events</a>
|
||||
<a href="{{ url('/api/events') }}" class="text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition">API</a>
|
||||
</nav>
|
||||
/* Auth area */
|
||||
.site-header .auth-area { display: flex; align-items: center; gap: 10px; }
|
||||
.site-header .btn-outline {
|
||||
padding: 7px 16px; border: 1.5px solid #d1d5db; border-radius: 8px;
|
||||
font-size: 0.875em; font-weight: 500; color: #374151; text-decoration: none;
|
||||
background: white; transition: border-color 0.2s, color 0.2s;
|
||||
}
|
||||
.site-header .btn-outline:hover { border-color: #667eea; color: #667eea; }
|
||||
.site-header .btn-primary {
|
||||
padding: 7px 16px; background: linear-gradient(135deg,#667eea 0%,#764ba2 100%);
|
||||
border-radius: 8px; font-size: 0.875em; font-weight: 600; color: white;
|
||||
text-decoration: none; border: none; cursor: pointer; transition: opacity 0.2s;
|
||||
}
|
||||
.site-header .btn-primary:hover { opacity: 0.9; }
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
@auth
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">{{ Auth::user()->name }}</span>
|
||||
<a href="{{ route('profile') }}" class="inline-block px-3 py-2 bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-white rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition text-sm">Profil</a>
|
||||
<form method="POST" action="{{ route('logout') }}" class="inline">
|
||||
/* Avatar dropdown */
|
||||
.avatar-wrapper { position: relative; }
|
||||
.avatar-btn {
|
||||
width: 38px; height: 38px; border-radius: 50%;
|
||||
background: linear-gradient(135deg,#667eea 0%,#764ba2 100%);
|
||||
border: none; cursor: pointer; display: flex; align-items: center;
|
||||
justify-content: center; color: white; font-weight: 700; font-size: 0.95em;
|
||||
letter-spacing: 0.02em; transition: box-shadow 0.2s; flex-shrink: 0;
|
||||
}
|
||||
.avatar-btn:hover { box-shadow: 0 0 0 3px rgba(102,126,234,0.25); }
|
||||
.avatar-dropdown {
|
||||
display: none;
|
||||
position: absolute; right: 0; top: calc(100% + 10px);
|
||||
background: white; border: 1px solid #e5e7eb; border-radius: 12px;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.12); min-width: 220px; overflow: hidden;
|
||||
z-index: 200;
|
||||
}
|
||||
.avatar-dropdown.open { display: block; }
|
||||
.avatar-dropdown .dd-header {
|
||||
padding: 14px 16px 12px; border-bottom: 1px solid #f3f4f6;
|
||||
}
|
||||
.avatar-dropdown .dd-name { font-weight: 700; font-size: 0.95em; color: #1a1a2e; }
|
||||
.avatar-dropdown .dd-email { font-size: 0.78em; color: #9ca3af; margin-top: 2px; }
|
||||
.avatar-dropdown .dd-link {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 11px 16px; font-size: 0.875em; color: #374151;
|
||||
text-decoration: none; transition: background 0.15s;
|
||||
}
|
||||
.avatar-dropdown .dd-link:hover { background: #f9fafb; color: #667eea; }
|
||||
.avatar-dropdown .dd-link svg { width: 16px; height: 16px; flex-shrink: 0; }
|
||||
.avatar-dropdown .dd-divider { border: none; border-top: 1px solid #f3f4f6; margin: 0; }
|
||||
.avatar-dropdown .dd-logout-form { margin: 0; }
|
||||
.avatar-dropdown .dd-logout-btn {
|
||||
width: 100%; display: flex; align-items: center; gap: 10px;
|
||||
padding: 11px 16px; font-size: 0.875em; color: #dc2626;
|
||||
background: none; border: none; cursor: pointer; transition: background 0.15s;
|
||||
text-align: left;
|
||||
}
|
||||
.avatar-dropdown .dd-logout-btn:hover { background: #fef2f2; }
|
||||
.avatar-dropdown .dd-logout-btn svg { width: 16px; height: 16px; flex-shrink: 0; }
|
||||
</style>
|
||||
|
||||
<header class="site-header">
|
||||
<div class="inner">
|
||||
<a href="{{ url('/') }}" class="logo">Veranstaltungs<span>-App</span></a>
|
||||
|
||||
<nav>
|
||||
<a href="{{ url('/') }}">Home</a>
|
||||
<a href="{{ route('events') }}">Events</a>
|
||||
</nav>
|
||||
|
||||
<div class="auth-area">
|
||||
@auth
|
||||
{{-- Avatar mit Dropdown --}}
|
||||
<div class="avatar-wrapper" id="avatarWrapper">
|
||||
<button class="avatar-btn" id="avatarBtn" title="{{ Auth::user()->name }}">
|
||||
{{ strtoupper(substr(Auth::user()->name, 0, 1)) }}
|
||||
</button>
|
||||
<div class="avatar-dropdown" id="avatarDropdown">
|
||||
<div class="dd-header">
|
||||
<div class="dd-name">{{ Auth::user()->name }}</div>
|
||||
<div class="dd-email">{{ Auth::user()->email }}</div>
|
||||
</div>
|
||||
<a href="{{ route('profile') }}" class="dd-link">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||
</svg>
|
||||
Mein Profil
|
||||
</a>
|
||||
<hr class="dd-divider">
|
||||
<form method="POST" action="{{ route('logout') }}" class="dd-logout-form">
|
||||
@csrf
|
||||
<button type="submit" class="inline-block px-3 py-2 bg-red-600 text-white rounded hover:bg-red-700 transition text-sm">Logout</button>
|
||||
<button type="submit" class="dd-logout-btn">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
|
||||
</svg>
|
||||
Abmelden
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@else
|
||||
<a href="{{ route('login') }}" class="inline-block px-4 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition text-sm font-medium">Anmelden</a>
|
||||
<a href="{{ route('register') }}" class="inline-block px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition text-sm font-medium">Registrieren</a>
|
||||
@endauth
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<a href="{{ route('login') }}" class="btn-outline">Anmelden</a>
|
||||
<a href="{{ route('register') }}" class="btn-primary">Registrieren</a>
|
||||
@endauth
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var btn = document.getElementById('avatarBtn');
|
||||
var dropdown = document.getElementById('avatarDropdown');
|
||||
if (!btn || !dropdown) return;
|
||||
|
||||
btn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
dropdown.classList.toggle('open');
|
||||
});
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!dropdown.contains(e.target)) {
|
||||
dropdown.classList.remove('open');
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
@ -3,116 +3,163 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Mein Profil</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<title>Mein Profil – Veranstaltungs-App</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f9f9f9; color: #1a1a2e; }
|
||||
main { max-width: 760px; margin: 40px auto; padding: 0 20px 80px; }
|
||||
.page-title { font-size: 1.8em; font-weight: 800; margin-bottom: 28px; letter-spacing: -0.02em; }
|
||||
|
||||
/* Alert boxes */
|
||||
.alert { padding: 12px 16px; border-radius: 8px; margin-bottom: 20px; font-size: 0.9em; font-weight: 500; }
|
||||
.alert-success { background: #ecfdf5; color: #065f46; border: 1px solid #6ee7b7; }
|
||||
.alert-error { background: #fef2f2; color: #991b1b; border: 1px solid #fca5a5; }
|
||||
|
||||
/* Cards */
|
||||
.card { background: white; border-radius: 12px; border: 1px solid #e5e7eb; margin-bottom: 24px; overflow: hidden; }
|
||||
.card-header { padding: 18px 24px; border-bottom: 1px solid #f3f4f6; }
|
||||
.card-header h2 { font-size: 1.05em; font-weight: 700; }
|
||||
.card-header p { font-size: 0.85em; color: #6b7280; margin-top: 3px; }
|
||||
.card-body { padding: 24px; }
|
||||
|
||||
/* Info-grid */
|
||||
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||||
@media(max-width:560px){ .info-grid { grid-template-columns: 1fr; } }
|
||||
.info-item label { display: block; font-size: 0.78em; font-weight: 600; color: #6b7280; text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 4px; }
|
||||
.info-item .value { font-size: 1em; font-weight: 500; }
|
||||
.badge { display: inline-block; padding: 3px 10px; border-radius: 20px; font-size: 0.78em; font-weight: 700; background: #eff6ff; color: #1d4ed8; }
|
||||
.badge.admin { background: #fef3c7; color: #92400e; }
|
||||
|
||||
/* Forms */
|
||||
.form-group { margin-bottom: 16px; }
|
||||
.form-group label { display: block; font-size: 0.875em; font-weight: 600; margin-bottom: 6px; }
|
||||
.form-group input {
|
||||
width: 100%; padding: 10px 12px; border: 1.5px solid #d1d5db;
|
||||
border-radius: 8px; font-size: 0.95em; transition: border-color 0.2s;
|
||||
background: white;
|
||||
}
|
||||
.form-group input:focus { outline: none; border-color: #667eea; box-shadow: 0 0 0 3px rgba(102,126,234,0.12); }
|
||||
.form-group .hint { font-size: 0.78em; color: #9ca3af; margin-top: 4px; }
|
||||
.error-msg { font-size: 0.8em; color: #dc2626; margin-top: 4px; }
|
||||
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||||
@media(max-width:560px){ .form-row { grid-template-columns: 1fr; } }
|
||||
|
||||
/* Buttons */
|
||||
.btn-primary {
|
||||
padding: 10px 22px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white; border: none; border-radius: 8px; font-size: 0.9em;
|
||||
font-weight: 600; cursor: pointer; transition: opacity 0.2s; width: 100%;
|
||||
}
|
||||
.btn-primary:hover { opacity: 0.9; }
|
||||
.divider { border: none; border-top: 1px solid #f3f4f6; margin: 20px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 dark:bg-gray-900">
|
||||
<div class="min-h-screen">
|
||||
<!-- Header -->
|
||||
<header class="bg-white dark:bg-gray-800 shadow">
|
||||
<div class="max-w-4xl mx-auto px-4 py-4 sm:px-6 lg:px-8 flex items-center justify-between">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Veranstaltungs-App</h1>
|
||||
<div class="flex gap-2">
|
||||
<a href="{{ route('events') }}" class="text-blue-600 hover:text-blue-800">Events</a>
|
||||
<form method="POST" action="{{ route('logout') }}" class="inline">
|
||||
@csrf
|
||||
<button type="submit" class="text-blue-600 hover:text-blue-800">Logout</button>
|
||||
</form>
|
||||
<body>
|
||||
@include('partials.header')
|
||||
<main>
|
||||
<h1 class="page-title">Mein Profil</h1>
|
||||
|
||||
{{-- Success / Error messages --}}
|
||||
@if(session('success_info'))
|
||||
<div class="alert alert-success">✓ {{ session('success_info') }}</div>
|
||||
@endif
|
||||
@if(session('success_pw'))
|
||||
<div class="alert alert-success">✓ {{ session('success_pw') }}</div>
|
||||
@endif
|
||||
|
||||
{{-- ── Profil-Übersicht ── --}}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Profilübersicht</h2>
|
||||
<p>Deine aktuellen Kontodaten auf einen Blick</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<label>Name</label>
|
||||
<span class="value">{{ $user->name }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>E‑Mail</label>
|
||||
<span class="value">{{ $user->email }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>Rolle</label>
|
||||
<span class="badge {{ $user->role === 'admin' ? 'admin' : '' }}">
|
||||
{{ $user->role === 'admin' ? 'Administrator' : ($user->role === 'organizer' ? 'Veranstalter' : 'Nutzer') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>Mitglied seit</label>
|
||||
<span class="value">{{ $user->created_at->format('d.m.Y') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="max-w-4xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
<!-- Profile Card -->
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white">Mein Profil</h2>
|
||||
</div>
|
||||
<div class="px-6 py-6">
|
||||
<dl class="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Name</dt>
|
||||
<dd class="mt-1 text-lg text-gray-900 dark:text-white">{{ Auth::user()->name }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">E-Mail</dt>
|
||||
<dd class="mt-1 text-lg text-gray-900 dark:text-white">{{ Auth::user()->email }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Rolle</dt>
|
||||
<dd class="mt-1">
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
||||
@if(Auth::user()->role === 'admin')
|
||||
Administrator
|
||||
@elseif(Auth::user()->role === 'organizer')
|
||||
Veranstalter
|
||||
@else
|
||||
Benutzer
|
||||
@endif
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Registriert am</dt>
|
||||
<dd class="mt-1 text-lg text-gray-900 dark:text-white">
|
||||
{{ Auth::user()->created_at->format('d.m.Y H:i') }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API Endpoints Card -->
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white">Verfügbare API Endpoints</h2>
|
||||
</div>
|
||||
<div class="px-6 py-6">
|
||||
<div class="space-y-4">
|
||||
<div class="border-l-4 border-blue-500 pl-4">
|
||||
<p class="font-mono text-sm text-gray-900 dark:text-white">GET /api/user/profile</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Dein Profil abrufen</p>
|
||||
</div>
|
||||
<div class="border-l-4 border-blue-500 pl-4">
|
||||
<p class="font-mono text-sm text-gray-900 dark:text-white">GET /api/user/events</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Deine erstellten Events</p>
|
||||
</div>
|
||||
<div class="border-l-4 border-blue-500 pl-4">
|
||||
<p class="font-mono text-sm text-gray-900 dark:text-white">GET /api/user/favorites</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Deine Favoriten</p>
|
||||
</div>
|
||||
<div class="border-l-4 border-blue-500 pl-4">
|
||||
<p class="font-mono text-sm text-gray-900 dark:text-white">POST /api/user/favorites/{id}/toggle</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Event zu Favoriten hinzufügen/entfernen</p>
|
||||
</div>
|
||||
<div class="border-l-4 border-blue-500 pl-4">
|
||||
<p class="font-mono text-sm text-gray-900 dark:text-white">GET /api/user/stats</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Deine Statistiken</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Links Card -->
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white">Schnelllinks</h2>
|
||||
</div>
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex gap-4 flex-wrap">
|
||||
<a href="{{ route('events') }}" class="inline-block px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
|
||||
Zu Events
|
||||
</a>
|
||||
<a href="{{ route('forgot-password') }}" class="inline-block px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-white rounded hover:bg-gray-300">
|
||||
Passwort ändern
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Name & E-Mail ändern ── --}}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Name & E‑Mail ändern</h2>
|
||||
<p>Passe deinen Anzeigenamen und deine E‑Mail-Adresse an</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if($errors->has('name') || $errors->has('email'))
|
||||
<div class="alert alert-error">Bitte korrigiere die markierten Felder.</div>
|
||||
@endif
|
||||
<form method="POST" action="{{ route('profile.update.info') }}">
|
||||
@csrf @method('PUT')
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" id="name" name="name" value="{{ old('name', $user->name) }}" required>
|
||||
@error('name')<span class="error-msg">{{ $message }}</span>@enderror
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">E‑Mail</label>
|
||||
<input type="email" id="email" name="email" value="{{ old('email', $user->email) }}" required>
|
||||
@error('email')<span class="error-msg">{{ $message }}</span>@enderror
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">Änderungen speichern</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Passwort ändern ── --}}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Passwort ändern</h2>
|
||||
<p>Wähle ein sicheres Passwort mit mindestens 8 Zeichen</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if($errors->has('current_password') || $errors->has('password'))
|
||||
<div class="alert alert-error">Bitte korrigiere die markierten Felder.</div>
|
||||
@endif
|
||||
<form method="POST" action="{{ route('profile.update.password') }}">
|
||||
@csrf @method('PUT')
|
||||
<div class="form-group">
|
||||
<label for="current_password">Aktuelles Passwort</label>
|
||||
<input type="password" id="current_password" name="current_password" required autocomplete="current-password">
|
||||
@error('current_password')<span class="error-msg">{{ $message }}</span>@enderror
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="password">Neues Passwort</label>
|
||||
<input type="password" id="password" name="password" required autocomplete="new-password">
|
||||
<span class="hint">Mindestens 8 Zeichen</span>
|
||||
@error('password')<span class="error-msg">{{ $message }}</span>@enderror
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password_confirmation">Neues Passwort bestätigen</label>
|
||||
<input type="password" id="password_confirmation" name="password_confirmation" required autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">Passwort ändern</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
@include('partials.footer')
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\EventWebController;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Http\Controllers\UserController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
@ -9,16 +10,22 @@ Route::get('/', [EventWebController::class, 'index'])->name('home');
|
||||
|
||||
// Event Routes
|
||||
Route::get('/events', [EventWebController::class, 'index'])->name('events');
|
||||
Route::get('/events/{event}', [EventWebController::class, 'show'])->name('events.show');
|
||||
Route::get('/events/{event}', [EventWebController::class, 'show'])->name('event.detail');
|
||||
|
||||
// Auth Routes
|
||||
Route::get('/login', function () {
|
||||
return view('auth.login');
|
||||
})->name('login');
|
||||
})->name('login')->middleware('guest');
|
||||
|
||||
Route::post('/login', [\App\Http\Controllers\AuthController::class, 'webLogin'])->name('login.post')->middleware('guest');
|
||||
|
||||
Route::get('/register', function () {
|
||||
return view('auth.register');
|
||||
})->name('register');
|
||||
})->name('register')->middleware('guest');
|
||||
|
||||
Route::post('/register', [\App\Http\Controllers\AuthController::class, 'webRegister'])->name('register.post')->middleware('guest');
|
||||
|
||||
Route::post('/logout', [\App\Http\Controllers\AuthController::class, 'webLogout'])->name('logout');
|
||||
|
||||
Route::get('/forgot-password', function () {
|
||||
return view('auth.forgot-password');
|
||||
@ -29,13 +36,16 @@ Route::get('/reset-password', function () {
|
||||
})->name('reset-password');
|
||||
|
||||
// Protected Routes
|
||||
Route::middleware('auth:sanctum')->group(function () {
|
||||
Route::get('/profile', function () {
|
||||
return view('profile');
|
||||
})->name('profile');
|
||||
|
||||
Route::post('/logout', [\App\Http\Controllers\AuthController::class, 'logout'])->name('logout');
|
||||
Route::middleware('auth')->group(function () {
|
||||
Route::get('/profile', [ProfileController::class, 'show'])->name('profile');
|
||||
Route::put('/profile/info', [ProfileController::class, 'updateInfo'])->name('profile.update.info');
|
||||
Route::put('/profile/password', [ProfileController::class, 'updatePassword'])->name('profile.update.password');
|
||||
});
|
||||
|
||||
// Swagger API Documentation Routes
|
||||
require __DIR__ . '/swagger.php';
|
||||
|
||||
// Legal pages
|
||||
Route::view('/datenschutz', 'legal.privacy')->name('privacy');
|
||||
Route::view('/impressum', 'legal.impressum')->name('impressum');
|
||||
Route::view('/agb', 'legal.agb')->name('agb');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user