User-Panel gebaut

This commit is contained in:
Jason Hirsch
2025-02-16 21:47:35 +01:00
parent 835de6fbf9
commit c17d39df24
280 changed files with 19492 additions and 0 deletions

View File

@@ -0,0 +1,136 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
/* Hier kannst du noch eigene Anpassungen vornehmen */
</style>
</head>
<body class="bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-100">
<!-- Navigation -->
<nav class="bg-white dark:bg-gray-800 shadow">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<div class="flex-shrink-0 flex items-center">
<img class="h-8 w-auto" src="{{ url_for('static', filename='clickcandit.png') }}" alt="Logo">
</div>
<div class="hidden sm:-my-px sm:ml-6 sm:flex sm:space-x-8">
<a href="{{ url_for('dashboard') }}" class="border-indigo-500 text-gray-900 dark:text-gray-100 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">Dashboard</a>
<a href="{{ url_for('admin') }}" class="border-transparent text-gray-500 dark:text-gray-400 hover:border-gray-300 hover:text-gray-700 dark:hover:text-gray-200 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">Admin Panel</a>
</div>
</div>
<div class="hidden sm:ml-6 sm:flex sm:items-center">
<div class="ml-3 relative">
<button type="button" class="max-w-xs bg-white dark:bg-gray-800 flex items-center text-sm rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" id="user-menu-button">
<span class="sr-only">Benutzermenü öffnen</span>
<img class="h-8 w-8 rounded-full" src="https://via.placeholder.com/150" alt="User Avatar">
</button>
<div class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 hidden" id="user-dropdown">
<a href="{{ url_for('dashboard') }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700">Dashboard</a>
<a href="{{ url_for('logout') }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700">Abmelden</a>
</div>
</div>
</div>
<div class="-mr-2 flex items-center sm:hidden">
<button type="button" class="bg-white dark:bg-gray-800 inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500" id="mobile-menu-button">
<span class="sr-only">Menü öffnen</span>
<svg class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
<svg class="hidden h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile Menü -->
<div class="sm:hidden hidden" id="mobile-menu">
<div class="pt-2 pb-3 space-y-1">
<a href="{{ url_for('dashboard') }}" class="bg-indigo-50 border-indigo-500 text-indigo-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium">Dashboard</a>
<a href="{{ url_for('admin') }}" class="border-transparent text-gray-600 dark:text-gray-400 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-800 dark:hover:text-gray-200 block pl-3 pr-4 py-2 border-l-4 text-base font-medium">Admin Panel</a>
</div>
<div class="pt-4 pb-3 border-t border-gray-200 dark:border-gray-700">
<div class="flex items-center px-4">
<div class="flex-shrink-0">
<img class="h-10 w-10 rounded-full" src="https://via.placeholder.com/150" alt="User Avatar">
</div>
<div class="ml-3">
<div class="text-base font-medium text-gray-800 dark:text-gray-100">{{ user }}</div>
<div class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ session['user_email'] if session.get('user_email') else '' }}</div>
</div>
</div>
<div class="mt-3 space-y-1">
<a href="{{ url_for('dashboard') }}" class="block px-4 py-2 text-base font-medium text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700">Dashboard</a>
<a href="{{ url_for('logout') }}" class="block px-4 py-2 text-base font-medium text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700">Abmelden</a>
</div>
</div>
</div>
</nav>
<!-- Hauptinhalt -->
<main class="max-w-7xl mx-auto p-6">
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-300 mb-4">Benutzerverwaltung</h2>
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Übersicht aller Benutzer</h3>
<p class="mt-1 max-w-2xl text-sm text-gray-500">Alle registrierten Benutzer werden hier aufgelistet.</p>
</div>
<div class="border-t border-gray-200 dark:border-gray-700">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Rolle</th>
<th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Aktionen</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
{% for user in users %}
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">{{ user.id }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">{{ user.name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ user.email }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ user.role }}</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a href="{{ url_for('edit_bookmarks', user_id=user.id) }}" class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 mr-4">Lesezeichen bearbeiten</a>
{% if session['user_id'] != user.id %}
<form action="{{ url_for('delete_user', user_id=user.id) }}" method="POST" class="inline">
<button type="submit" class="text-red-600 dark:text-red-400 hover:text-red-900" onclick="return confirm('Benutzer wirklich löschen?');">Löschen</button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="mt-6 flex justify-between items-center">
<a href="{{ url_for('register') }}" class="bg-indigo-600 text-white px-4 py-2 rounded hover:bg-indigo-700">Neuen Benutzer anlegen</a>
<a href="{{ url_for('dashboard') }}" class="text-indigo-600 hover:underline">Zurück zum Dashboard</a>
</div>
</main>
<script>
// Mobile Menü
const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
mobileMenuButton.addEventListener('click', () => {
mobileMenu.style.display = mobileMenu.style.display === 'none' || mobileMenu.style.display === '' ? 'block' : 'none';
});
// User-Dropdown
const userMenuButton = document.getElementById('user-menu-button');
const userDropdown = document.getElementById('user-dropdown');
userMenuButton.addEventListener('click', () => {
userDropdown.classList.toggle('hidden');
});
</script>
</body>
</html>

View File

@@ -0,0 +1,610 @@
<!DOCTYPE html>
<html lang="de" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard</title>
<!-- Tailwind & FontAwesome -->
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
<!-- GSAP (Animations) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#10b981',
}
}
}
}
</script>
<style>
/* Hintergrundbild per CSS (aus settings) */
body {
background-image: url('{{ url_for("static", filename=wallpaper) }}');
background-size: cover;
background-position: center;
background-attachment: fixed;
}
.glassmorphism {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 10px;
}
.dark .glassmorphism {
background: rgba(0, 0, 0, 0.2);
}
/* Eingabe-Felder in Dark Mode */
.dark input[type="text"],
.dark input[type="email"],
.dark input[type="password"],
.dark textarea,
.dark select {
background-color: #374151;
color: #fff;
}
.dock-icon {
transition: all 0.3s ease;
}
.dock-icon:hover {
transform: scale(1.1);
}
</style>
</head>
<body class="min-h-screen bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-white transition-colors duration-300">
<div class="container mx-auto p-4 sm:p-6 lg:p-8 pb-20 flex flex-col items-center">
<!-- Bookmark Modal (Pop-up für Lesezeichen) -->
<div id="bookmarkModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-white dark:bg-gray-800 glassmorphism p-6 rounded-lg max-w-md w-full mx-4">
<h2 class="text-2xl font-bold mb-4 text-gray-900 dark:text-white">Lesezeichen</h2>
<div class="mb-4">
<input type="text" id="bookmarkInput" placeholder="URL eingeben..." class="w-full p-2 border rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
</div>
<div class="flex justify-between">
<button id="addBookmark" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded transition-colors duration-200">Hinzufügen</button>
<button class="bg-gray-300 dark:bg-gray-600 hover:bg-gray-400 dark:hover:bg-gray-500 text-gray-800 dark:text-white px-4 py-2 rounded transition-colors duration-200 close-modal">Schließen</button>
</div>
<div class="mt-6">
<h3 class="text-lg font-semibold mb-2 text-gray-900 dark:text-white">Meine Lesezeichen</h3>
<ul id="bookmarksList" class="space-y-2 max-h-60 overflow-y-auto">
<!-- Dynamisch per JS -->
</ul>
</div>
</div>
</div>
<!-- Bookmark-Button (öffnet das Modal) -->
<button id="bookmarkButton" class="fixed top-4 left-4 z-10 w-12 h-12 bg-blue-500 hover:bg-blue-600 text-white rounded-xl transition-colors duration-200 flex items-center justify-center">
<i class="fas fa-bookmark text-xl"></i>
</button>
<!-- Dark Mode Toggle (Sonne/Mond) -->
<button id="darkModeToggle" class="fixed top-4 right-4 w-12 h-12 rounded-xl bg-gray-200 dark:bg-gray-800 z-10 transition-colors duration-300">
<i class="fas fa-sun text-yellow-400 dark:hidden text-xl"></i>
<i class="fas fa-moon text-blue-200 hidden dark:inline text-xl"></i>
</button>
<!-- Logo, Begrüßung, Uhr -->
<div class="flex flex-col items-center justify-center space-y-4 text-center mb-6">
<a id="logo-link" href="https://{{ domain }}" target="_blank">
<img id="logo-img" src="{{ logo_path }}" alt="Firmenlogo" class="w-20 h-auto mb-2">
</a>
<h1 class="text-2xl sm:text-3xl font-bold text-gray-800 dark:text-white transition-colors duration-300">
Willkommen zurück, {{ user }}
</h1>
<div id="clock" class="text-xl sm:text-2xl font-semibold"></div>
</div>
<!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="mb-4">
{% for category, message in messages %}
<div class="alert flex items-center bg-{{ category }}-500 text-white text-sm font-bold px-4 py-3" role="alert">
<p>{{ message }}</p>
<svg class="fill-current h-6 w-6 text-white ml-auto close-flash" role="button" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<title>Schließen</title>
<path d="M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697L8.302 10 5.651 7.349a1.2 1.2 0 1 1 1.697-1.697L10 8.181l2.651-2.529a1.2 1.2 0 1 1 1.697 1.697L11.698 10l2.651 2.651a1.2 1.2 0 0 1 0 1.698z"/>
</svg>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<!-- Widgets (Wetter, Speicher, usw.) -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6 w-full max-w-3xl">
<!-- Wetter-Box -->
<div class="glassmorphism p-2 sm:p-3 shadow-lg rounded-md">
<h2 class="text-xs sm:text-sm font-semibold mb-1 text-center sm:text-left">Wetter in {{ city }}</h2>
<div class="flex items-center justify-center sm:justify-start mb-2">
<i class="fas {{ weather_icon }} text-base sm:text-lg md:text-xl mr-1"></i>
<p class="text-sm sm:text-base md:text-lg font-bold">{{ current_temp }}°C</p>
</div>
<!-- Forecast -->
<div class="space-y-1">
{% for day in forecast %}
<div class="flex items-center justify-between rounded-sm p-1">
<div class="flex items-center">
<i class="fas {{ day.weather_icon }} text-2xs sm:text-xs mr-1"></i>
<p class="text-2xs sm:text-xs">{{ day.date }}</p>
</div>
<p class="text-2xs sm:text-xs font-semibold">{{ day.day.avgtemp_c }}°C</p>
</div>
{% endfor %}
</div>
</div>
<!-- Beispiel: Speicher / RAM (nur Demo, falls du Systemdaten abrufst) -->
<div class="glassmorphism p-4 shadow-lg">
<h2 class="text-lg font-semibold mb-2">Speicher</h2>
<p class="text-xl font-bold mb-1">— Demo —</p>
</div>
<div class="glassmorphism p-4 shadow-lg">
<h2 class="text-lg font-semibold mb-2">RAM-Nutzung</h2>
<p class="text-xl font-bold mb-1">— Demo —</p>
</div>
</div>
<!-- App Launcher (Carousel) -->
<div class="glassmorphism p-4 mb-12 relative w-full max-w-3xl">
<div id="appCarousel" class="overflow-hidden">
<div class="flex transition-transform duration-300 ease-in-out">
{% for app_chunk in user_app_chunks %}
<div class="w-full flex-shrink-0">
<div class="grid grid-cols-3 sm:grid-cols-5 gap-4">
{% for app in app_chunk %}
<a href="https://{{ app.subdomain }}.{{ domain }}" class="flex flex-col items-center" title="{{ app.appkey }}">
<div class="w-12 h-12 flex items-center justify-center {{ app.bg_color }} rounded-xl mb-1 hover:scale-110 transition-transform">
<i class="{{ app.icon_class }} text-white text-xl"></i>
</div>
<p class="text-center text-xs font-medium">{{ app.name }}</p>
</a>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
<button id="prevSlide" class="absolute top-1/2 left-2 transform -translate-y-1/2 bg-white/50 dark:bg-black/50 text-gray-800 dark:text-white rounded-full p-1 hover:bg-white/70 dark:hover:bg-black/70 transition-colors duration-200">
<i class="fas fa-chevron-left text-sm"></i>
</button>
<button id="nextSlide" class="absolute top-1/2 right-2 transform -translate-y-1/2 bg-white/50 dark:bg-black/50 text-gray-800 dark:text-white rounded-full p-1 hover:bg-white/70 dark:hover:bg-black/70 transition-colors duration-200">
<i class="fas fa-chevron-right text-sm"></i>
</button>
</div>
</div>
<!-- Dock mit Buttons: Home, Suche, Settings, Support -->
<div class="fixed bottom-4 left-1/2 transform -translate-x-1/2 glassmorphism p-2 flex space-x-4">
<button class="dock-icon w-12 h-12 flex items-center justify-center bg-blue-500 hover:bg-blue-600 rounded-xl" data-modal="homeModal">
<i class="fas fa-home text-white text-xl"></i>
</button>
<button class="dock-icon w-12 h-12 flex items-center justify-center bg-green-500 hover:bg-green-600 rounded-xl" data-modal="searchModal">
<i class="fas fa-search text-white text-xl"></i>
</button>
<button class="dock-icon w-12 h-12 flex items-center justify-center bg-yellow-500 hover:bg-yellow-600 rounded-xl" data-modal="settingsModal">
<i class="fas fa-cog text-white text-xl"></i>
</button>
<button class="dock-icon w-12 h-12 flex items-center justify-center bg-purple-500 hover:bg-purple-600 rounded-xl" data-modal="supportModal">
<i class="fas fa-question-circle text-white text-xl"></i>
</button>
</div>
<!-- Home Modal (Konto löschen, Daten-Export, Logout) -->
<div id="homeModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-white dark:bg-gray-800 glassmorphism p-6 rounded-lg max-w-md w-full mx-auto shadow-lg">
<h2 class="text-2xl font-bold mb-4 text-gray-900 dark:text-white">
Willkommen Zuhause, {{ user }}!
</h2>
<p class="mb-4">Schneller Zugriff auf wichtige Funktionen</p>
<!-- Datenexport -->
<form action="{{ url_for('request_data_export') }}" method="GET" class="mb-3">
<button type="submit" class="w-full bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded transition ease-in-out duration-150">
Daten exportieren
</button>
</form>
<!-- Konto löschen -->
<form action="{{ url_for('delete_account') }}" method="POST">
<button type="submit" class="w-full bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded transition ease-in-out duration-150" onclick="return confirm('Sind Sie sicher, dass Sie Ihr Konto löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.');">
Konto löschen
</button>
</form>
<!-- Logout -->
<form action="{{ url_for('logout') }}" method="POST" class="mb-3 mt-2">
<button type="submit" class="w-full bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition ease-in-out duration-150">
Abmelden
</button>
</form>
<button class="mt-4 bg-gray-300 dark:bg-gray-700 hover:bg-gray-400 dark:hover:bg-gray-800 text-gray-800 dark:text-white font-bold py-2 px-4 rounded transition ease-in-out duration-150 close-modal">
Schließen
</button>
</div>
</div>
<!-- Search Modal -->
<div id="searchModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center">
<div class="bg-white dark:bg-gray-800 glassmorphism p-6 rounded-lg max-w-md w-full mx-4">
<h2 class="text-2xl font-bold mb-4">Suche</h2>
<input type="text" id="searchInput" placeholder="Suchen..." class="w-full p-2 border rounded mb-4">
<select id="searchEngine" class="w-full p-2 border rounded mb-4">
<option value="qwant">Qwant</option>
<option value="google">Google</option>
</select>
<button id="startSearch" class="bg-green-500 text-white px-4 py-2 rounded">Suche starten</button>
<button class="mt-4 bg-blue-500 text-white px-4 py-2 rounded close-modal">Schließen</button>
</div>
</div>
<!-- Einstellungen-Modal -->
<div id="settingsModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center overflow-auto">
<div class="bg-white dark:bg-gray-800 glassmorphism p-6 rounded-lg w-full max-w-md max-h-screen overflow-y-auto mx-4">
<h2 class="text-2xl font-bold mb-4">Einstellungen</h2>
<p>Hier können Sie Ihre Einstellungen anpassen und das System nach Ihren Wünschen konfigurieren.</p>
<!-- Wallpaper-Auswahl -->
<h3 class="text-lg font-semibold mb-2">Hintergrundbild auswählen</h3>
<div id="wallpaperSelection" class="grid grid-cols-2 sm:grid-cols-3 gap-2 mb-4">
{% for i in range(1, 27) %}
<img src="{{ url_for('static', filename=i ~ '.png') }}"
alt="Wallpaper {{ i }}"
class="w-full h-auto wallpaper-thumb cursor-pointer border-2
{{ 'border-blue-500' if wallpaper == i ~ '.png' else 'border-transparent' }}
rounded"
data-wallpaper="{{ i }}.png">
{% endfor %}
</div>
<!-- Stadt -->
<h3 class="text-lg font-semibold mb-2">Stadt ändern</h3>
<input type="text" id="cityInput" class="w-full p-2 border rounded mb-4" placeholder="Geben Sie Ihre Stadt ein" value="{{ city }}">
<!-- Wettervorhersage Toggle (versteckt, aber im Code notwendig) -->
<div style="display: none;">
<h3 class="text-lg font-semibold mb-2">Wochenvorhersage anzeigen</h3>
<label class="inline-flex items-center mt-2">
<input type="checkbox"
id="weatherForecastToggle"
class="form-checkbox h-5 w-5 text-blue-600"
{% if show_forecast %} checked {% endif %}>
<span class="ml-2 text-gray-700 dark:text-gray-300">Wochenvorhersage anzeigen</span>
</label>
</div>
<!-- Nur für Admins: Benutzerverwaltung -->
{% if role == 'admin' %}
<div class="mt-6 p-4 bg-gray-100 dark:bg-gray-700 rounded">
<h3 class="text-lg font-semibold mb-2 text-gray-900 dark:text-white">Benutzerverwaltung</h3>
<p class="text-sm mb-3">
Als Admin kannst du hier neue Benutzer anlegen, bearbeiten oder löschen
</p>
<a href="{{ url_for('admin') }}"
class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">
Zum Admin-Panel
</a>
</div>
{% endif %}
<button id="saveSettings" class="mt-4 bg-yellow-500 text-white px-4 py-2 rounded hover:bg-yellow-600 transition-colors duration-200">Speichern</button>
<button class="mt-4 ml-2 bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600 transition-colors duration-200 close-modal">Schließen</button>
</div>
</div>
<!-- Support Modal -->
<div id="supportModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center">
<div class="bg-white dark:bg-gray-800 p-8 rounded-lg max-w-md w-full">
<h2 class="text-2xl font-bold mb-4">Support kontaktieren</h2>
<label for="problemType" class="block mb-2 text-lg font-semibold">Art des Problems</label>
<select id="problemType" class="w-full p-2 border rounded mb-4">
<option value="Technisches Problem">Technisches Problem</option>
<option value="Account Problem">Account Problem</option>
<option value="Sonstiges">Sonstiges</option>
</select>
<label for="emailInput" class="block mb-2 text-lg font-semibold">Ihre E-Mail</label>
<input type="email" id="emailInput" class="w-full p-2 border rounded mb-4" placeholder="Ihre E-Mail-Adresse">
<label for="messageInput" class="block mb-2 text-lg font-semibold">Nachricht</label>
<textarea id="messageInput" class="w-full p-2 border rounded mb-4" rows="4" placeholder="Beschreiben Sie Ihr Problem"></textarea>
<button id="sendSupportMessage" class="bg-purple-500 text-white px-4 py-2 rounded hover:bg-purple-600 transition-colors duration-200">
Nachricht senden
</button>
<button class="mt-4 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition-colors duration-200 close-modal">
Schließen
</button>
</div>
</div>
<!-- JavaScript für Modal, Dark Mode, Clock, App Carousel, Bookmarks, usw. -->
<script>
// Dark Mode
const darkModeToggle = document.getElementById('darkModeToggle');
function toggleDarkMode() {
document.documentElement.classList.toggle('dark');
localStorage.setItem('darkMode', document.documentElement.classList.contains('dark'));
}
darkModeToggle.addEventListener('click', toggleDarkMode);
if (localStorage.getItem('darkMode') === 'true' ||
(!('darkMode' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
}
// Uhr
function updateClock() {
const now = new Date();
const timeString = now.toLocaleTimeString('de-DE');
const dateString = now.toLocaleDateString('de-DE', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
document.getElementById('clock').innerHTML = `${timeString}<br><span class="text-sm font-normal">${dateString}</span>`;
}
setInterval(updateClock, 1000);
updateClock();
// Carousel
const carousel = document.getElementById('appCarousel');
const carouselContent = carousel.querySelector('.flex');
const prevButton = document.getElementById('prevSlide');
const nextButton = document.getElementById('nextSlide');
let currentSlide = 0;
const totalSlides = carouselContent.children.length;
function showSlide(index) {
currentSlide = index;
const offset = -currentSlide * 100;
gsap.to(carouselContent, {duration: 0.5, x: `${offset}%`, ease: "power2.inOut"});
}
prevButton.addEventListener('click', () => {
currentSlide = (currentSlide - 1 + totalSlides) % totalSlides;
showSlide(currentSlide);
});
nextButton.addEventListener('click', () => {
currentSlide = (currentSlide + 1) % totalSlides;
showSlide(currentSlide);
});
// Animations
gsap.from(".glassmorphism", {duration: 1, opacity: 0, y: 50, stagger: 0.2, ease: "power3.out"});
gsap.from(".dock-icon", {duration: 0.5, opacity: 0, y: 20, stagger: 0.1, ease: "back.out(1.7)", delay: 1});
// Modal-Fenster öffnen/schließen
const modals = document.querySelectorAll('[id$="Modal"]');
const modalTriggers = document.querySelectorAll('[data-modal]');
const closeButtons = document.querySelectorAll('.close-modal');
modalTriggers.forEach(trigger => {
trigger.addEventListener('click', () => {
const modalId = trigger.getAttribute('data-modal');
const modal = document.getElementById(modalId);
modal.classList.remove('hidden');
modal.classList.add('flex');
});
});
closeButtons.forEach(button => {
button.addEventListener('click', () => {
const modal = button.closest('[id$="Modal"]');
modal.classList.remove('flex');
modal.classList.add('hidden');
});
});
modals.forEach(modal => {
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.classList.remove('flex');
modal.classList.add('hidden');
}
});
});
// Wallpaper-Auswahl
let selectedWallpaper = '{{ wallpaper }}';
const wallpaperThumbnails = document.querySelectorAll('.wallpaper-thumb');
wallpaperThumbnails.forEach(thumb => {
thumb.addEventListener('click', function() {
wallpaperThumbnails.forEach(t => {
t.classList.remove('border-blue-500');
t.classList.add('border-transparent');
});
this.classList.remove('border-transparent');
this.classList.add('border-blue-500');
selectedWallpaper = this.getAttribute('data-wallpaper');
});
});
// Save Settings
document.getElementById('saveSettings').addEventListener('click', function() {
const city = document.getElementById('cityInput').value.trim();
const showForecast = document.getElementById('weatherForecastToggle').checked;
// Aktuelle Settings laden, um Bookmarks nicht zu überschreiben
fetch('/get_settings')
.then(res => res.json())
.then(settings => {
const bookmarks = settings.bookmarks || [];
fetch('/save_settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
'wallpaper': selectedWallpaper,
'city': city,
'show_forecast': showForecast,
'bookmarks': bookmarks
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Fehler beim Speichern der Einstellungen: ' + (data.message || 'unbekannt'));
}
});
});
});
// Websuche
document.getElementById('startSearch').addEventListener('click', () => {
const query = document.getElementById('searchInput').value.trim();
const engine = document.getElementById('searchEngine').value;
if (query) {
let searchURL = '';
if (engine === 'qwant') {
searchURL = `https://www.qwant.com/?q=${encodeURIComponent(query)}`;
} else {
searchURL = `https://www.google.com/search?q=${encodeURIComponent(query)}`;
}
window.open(searchURL, '_blank');
}
});
// Bookmarks
const bookmarkModal = document.getElementById('bookmarkModal');
const bookmarkButton = document.getElementById('bookmarkButton');
const bookmarkInput = document.getElementById('bookmarkInput');
const addBookmark = document.getElementById('addBookmark');
const bookmarksList = document.getElementById('bookmarksList');
function openBookmarkModal() {
bookmarkModal.classList.remove('hidden');
bookmarkModal.classList.add('flex');
loadBookmarks();
}
function closeBookmarkModal() {
bookmarkModal.classList.remove('flex');
bookmarkModal.classList.add('hidden');
}
bookmarkButton.addEventListener('click', openBookmarkModal);
bookmarkModal.addEventListener('click', (e) => {
if (e.target === bookmarkModal) {
closeBookmarkModal();
}
});
closeButtons.forEach(button => {
button.addEventListener('click', closeBookmarkModal);
});
addBookmark.addEventListener('click', () => {
let url = bookmarkInput.value.trim();
if (url) {
const urlPattern = /^https?:\/\//i;
if (!urlPattern.test(url)) {
alert('Bitte gib eine vollständige URL mit http:// oder https:// ein.');
return;
}
addBookmarkToList(url);
bookmarkInput.value = '';
saveBookmarks();
}
});
function addBookmarkToList(url) {
const li = document.createElement('li');
li.innerHTML = `
<div class="flex items-center justify-between p-2 bg-gray-100 dark:bg-gray-700 rounded">
<a href="${url}" target="_blank" class="text-blue-600 dark:text-blue-400 hover:underline truncate">${url}</a>
<button class="delete-bookmark text-red-500 hover:text-red-700">
<i class="fas fa-trash-alt"></i>
</button>
</div>
`;
bookmarksList.appendChild(li);
const deleteButton = li.querySelector('.delete-bookmark');
deleteButton.addEventListener('click', () => {
li.remove();
saveBookmarks();
});
}
function loadBookmarks() {
fetch('/get_settings')
.then(res => res.json())
.then(settings => {
const b = settings.bookmarks || [];
bookmarksList.innerHTML = '';
b.forEach(bookmark => addBookmarkToList(bookmark));
});
}
function saveBookmarks() {
const b = Array.from(bookmarksList.children).map(li => li.querySelector('a').href);
fetch('/get_settings')
.then(res => res.json())
.then(settings => {
const city = settings.city;
const wallpaper = settings.wallpaper_url.split('/').pop();
const show_forecast = settings.show_forecast;
fetch('/save_settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
bookmarks: b,
city: city,
wallpaper: wallpaper,
show_forecast: show_forecast
})
}).then(response => {
if (!response.ok) {
alert('Fehler beim Speichern der Lesezeichen.');
}
});
});
}
// Beim Laden sofort Lesezeichen laden
window.addEventListener('DOMContentLoaded', loadBookmarks);
// Support Modal
const supportModal = document.getElementById('supportModal');
const supportModalToggle = document.querySelector('[data-modal="supportModal"]');
supportModalToggle.addEventListener('click', () => {
supportModal.classList.remove('hidden');
supportModal.classList.add('flex');
});
const sendSupportMessage = document.getElementById('sendSupportMessage');
sendSupportMessage.addEventListener('click', () => {
const email = document.getElementById('emailInput').value.trim();
const problemType = document.getElementById('problemType').value;
const message = document.getElementById('messageInput').value.trim();
if (email && message) {
fetch('/send_support_message', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, problemType, message })
}).then(response => {
if (response.ok) {
alert('Ihre Nachricht wurde erfolgreich gesendet.');
supportModal.classList.remove('flex');
supportModal.classList.add('hidden');
} else {
alert('Fehler beim Senden der Nachricht.');
}
});
} else {
alert('Bitte füllen Sie alle Felder aus.');
}
});
// Flash-Nachrichten per Klick schließen
document.addEventListener('DOMContentLoaded', function () {
const flashCloseBtns = document.querySelectorAll('.close-flash');
flashCloseBtns.forEach(btn => {
btn.addEventListener('click', function () {
const alertBox = this.closest('.alert');
alertBox.style.transition = 'opacity 0.5s ease';
alertBox.style.opacity = '0';
setTimeout(() => alertBox.remove(), 500);
});
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,94 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lesezeichen bearbeiten</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-100">
<div class="max-w-4xl mx-auto p-6">
<!-- Header -->
<header class="mb-8">
<h1 class="text-3xl font-bold">Lesezeichen bearbeiten</h1>
<p class="mt-2 text-gray-600 dark:text-gray-400">
Bearbeiten Sie die Lesezeichen dieses Benutzers. Fügen Sie neue Einträge hinzu oder entfernen Sie bestehende.
</p>
</header>
<!-- Formular -->
<form id="editBookmarksForm" method="POST" action="{{ url_for('edit_bookmarks', user_id=user_id) }}">
<!-- Dynamische Bookmark-Liste -->
<div id="bookmarksContainer" class="mb-6 space-y-4">
{% for bookmark in bookmarks %}
<div class="flex items-center space-x-3">
<input type="url" name="bookmarks_list" value="{{ bookmark }}" class="flex-1 p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100">
<button type="button" class="deleteBookmark bg-red-500 text-white px-3 py-2 rounded hover:bg-red-600 focus:outline-none">
Löschen
</button>
</div>
{% endfor %}
</div>
<!-- Neuen Bookmark hinzufügen -->
<div class="mb-6">
<div class="flex items-center space-x-3">
<input type="url" id="newBookmarkInput" placeholder="https://example.com" class="flex-1 p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100">
<button type="button" id="addBookmarkBtn" class="bg-indigo-600 text-white px-3 py-2 rounded hover:bg-indigo-700 focus:outline-none">
Hinzufügen
</button>
</div>
</div>
<!-- Aktionen -->
<div class="flex space-x-4">
<button type="submit" class="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700 transition-colors focus:outline-none">
Speichern
</button>
<a href="{{ url_for('admin') }}" class="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600 transition-colors">
Zurück
</a>
</div>
</form>
</div>
<script>
const addBookmarkBtn = document.getElementById('addBookmarkBtn');
const newBookmarkInput = document.getElementById('newBookmarkInput');
const bookmarksContainer = document.getElementById('bookmarksContainer');
addBookmarkBtn.addEventListener('click', () => {
const url = newBookmarkInput.value.trim();
if(url === '') return;
// Optional: URL-Validierung
if(!/^https?:\/\//i.test(url)){
alert('Bitte geben Sie eine vollständige URL mit http:// oder https:// ein.');
return;
}
// Neues Element erstellen
const div = document.createElement('div');
div.className = 'flex items-center space-x-3';
const input = document.createElement('input');
input.type = 'url';
input.name = 'bookmarks_list';
input.value = url;
input.className = 'flex-1 p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100';
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'deleteBookmark bg-red-500 text-white px-3 py-2 rounded hover:bg-red-600 focus:outline-none';
btn.textContent = 'Löschen';
btn.addEventListener('click', () => {
div.remove();
});
div.appendChild(input);
div.appendChild(btn);
bookmarksContainer.appendChild(div);
newBookmarkInput.value = '';
});
// Vorhandene Löschen-Buttons initialisieren
document.querySelectorAll('.deleteBookmark').forEach(btn => {
btn.addEventListener('click', () => {
btn.parentElement.remove();
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - ClickCandit</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="flex items-center justify-center h-screen bg-gray-900">
<div class="w-full max-w-sm p-8 space-y-4 bg-gray-800 rounded shadow-md">
<h2 class="text-2xl font-bold text-center text-white">Login</h2>
<form method="POST">
<input type="email" name="email" placeholder="E-Mail" required
class="w-full p-2 text-white bg-gray-700 rounded focus:outline-none">
<input type="password" name="password" placeholder="Passwort" required
class="w-full p-2 mt-2 text-white bg-gray-700 rounded focus:outline-none">
<button type="submit" class="w-full p-2 mt-4 bg-blue-500 rounded hover:bg-blue-600">Login</button>
</form>
<p class="text-gray-400">Noch kein Konto? <a href="{{ url_for('register') }}" class="text-blue-400">Registrieren</a></p>
</div>
</body>
</html>

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Registrierung - ClickCandit</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="flex items-center justify-center h-screen bg-gray-900">
<div class="w-full max-w-sm p-8 space-y-4 bg-gray-800 rounded shadow-md">
<h2 class="text-2xl font-bold text-center text-white">Registrieren</h2>
<form method="POST">
<input type="text" name="name" placeholder="Name" required
class="w-full p-2 text-white bg-gray-700 rounded focus:outline-none">
<input type="email" name="email" placeholder="E-Mail" required
class="w-full p-2 mt-2 text-white bg-gray-700 rounded focus:outline-none">
<input type="password" name="password" placeholder="Passwort" required
class="w-full p-2 mt-2 text-white bg-gray-700 rounded focus:outline-none">
<button type="submit" class="w-full p-2 mt-4 bg-green-500 rounded hover:bg-green-600">Registrieren</button>
</form>
<p class="text-gray-400">Bereits ein Konto? <a href="{{ url_for('login') }}" class="text-blue-400">Login</a></p>
</div>
</body>
</html>