This commit is contained in:
2025-05-10 16:04:40 +01:00
2 changed files with 236 additions and 171 deletions

2
.env
View File

@@ -4,7 +4,7 @@
# Flask # Flask
FLASK_APP=app.py FLASK_APP=app.py
FLASK_DEBUG=1 FLASK_DEBUG=1
SECRET_KEY=your-secret-key-replace-in-production SECRET_KEY=ETMyh4JfBfvpZSscqfuzjCOAvelm5lEju
# OpenAI API # OpenAI API
OPENAI_API_KEY=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 OPENAI_API_KEY=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

View File

@@ -250,6 +250,36 @@
transform: translateY(-2px); transform: translateY(-2px);
} }
.profile-tab.active {
color: white;
background: rgba(179, 143, 255, 0.2);
border-bottom: 3px solid rgba(179, 143, 255, 0.7);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.profile-tab.active::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(90deg, #8B5CF6, #6366F1);
border-radius: 3px 3px 0 0;
}
/* Animation für Tab-Inhalte */
.tab-content {
transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
.tab-content.hidden {
display: none;
opacity: 0;
transform: translateY(10px);
}
/* Statistik-Elemente - verkleinert */ /* Statistik-Elemente - verkleinert */
.stat-item { .stat-item {
padding: 0.75rem !important; padding: 0.75rem !important;
@@ -322,7 +352,9 @@
<span><i class="fas fa-map-marker-alt"></i> {{ user.location if user.location else 'Kein Standort angegeben' }}</span> <span><i class="fas fa-map-marker-alt"></i> {{ user.location if user.location else 'Kein Standort angegeben' }}</span>
<span><i class="fas fa-calendar-alt"></i> Mitglied seit {{ user.created_at.strftime('%d.%m.%Y') }}</span> <span><i class="fas fa-calendar-alt"></i> Mitglied seit {{ user.created_at.strftime('%d.%m.%Y') }}</span>
</div> </div>
<button class="edit-profile-btn">Profil bearbeiten</button> <button class="edit-profile-btn mt-4 bg-purple-600 hover:bg-purple-700 text-white font-semibold py-2 px-4 rounded-lg transition-all duration-300 flex items-center">
<i class="fas fa-user-edit mr-2"></i> Profil bearbeiten
</button>
</div> </div>
</div> </div>
@@ -534,16 +566,25 @@
<div class="tab-content hidden" id="collections-tab"> <div class="tab-content hidden" id="collections-tab">
<div id="collections-container"> <div id="collections-container">
{% if collections %} {% if collections %}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for collection in collections %} {% for collection in collections %}
<div class="collection-item"> <div class="collection-item bg-opacity-70 rounded-xl overflow-hidden border border-gray-700/60 bg-gray-800/80 transition-all duration-300 hover:transform hover:scale-105 hover:shadow-lg">
<h3>{{ collection.title }}</h3> <div class="p-5">
<p>{{ collection.description }}</p> <h3 class="text-xl font-bold mb-2 text-purple-400">{{ collection.title }}</h3>
<div class="collection-meta"> <p class="mb-4 text-sm text-gray-300">{{ collection.description }}</p>
<div class="flex justify-between items-center text-xs text-gray-400">
<span>{{ collection.thoughts_count }} Gedanken</span> <span>{{ collection.thoughts_count }} Gedanken</span>
<span>{{ collection.date }}</span> <span>{{ collection.date }}</span>
</div> </div>
</div> </div>
<div class="p-3 border-t border-gray-700/60 bg-gray-900/80 flex justify-center">
<a href="#" class="text-purple-400 hover:text-purple-300 transition-colors">
<i class="fas fa-eye mr-1"></i> Anzeigen
</a>
</div>
</div>
{% endfor %} {% endfor %}
</div>
{% else %} {% else %}
<div class="text-center py-12"> <div class="text-center py-12">
<i class="fas fa-folder-open text-5xl text-gray-400 mb-4"></i> <i class="fas fa-folder-open text-5xl text-gray-400 mb-4"></i>
@@ -557,27 +598,29 @@
<div class="tab-content hidden" id="connections-tab"> <div class="tab-content hidden" id="connections-tab">
<div id="connections-container"> <div id="connections-container">
{% if connections %} {% if connections %}
<div class="grid grid-cols-1 gap-6">
{% for connection in connections %} {% for connection in connections %}
<div class="connection-item"> <div class="connection-item bg-opacity-70 rounded-xl p-4 border border-gray-700/60 bg-gray-800/80 transition-all duration-300 hover:bg-gray-800/95">
<div class="connection-nodes"> <div class="connection-nodes flex items-stretch">
<div class="connection-node"> <div class="connection-node flex-1 p-4 rounded-lg bg-gray-900/80 text-white">
<h4>{{ connection.source.title }}</h4> <h4 class="font-semibold mb-1">{{ connection.source.title }}</h4>
<p>{{ connection.source.excerpt }}</p> <p class="text-sm text-gray-400">{{ connection.source.excerpt }}</p>
</div> </div>
<div class="connection-type"> <div class="connection-type flex flex-col items-center justify-center px-4">
<i class="fas fa-arrow-right"></i> <i class="fas fa-arrow-right text-purple-400 mb-1"></i>
<span>{{ connection.relation_type }}</span> <span class="text-xs text-gray-500">{{ connection.relation_type }}</span>
</div> </div>
<div class="connection-node"> <div class="connection-node flex-1 p-4 rounded-lg bg-gray-900/80 text-white">
<h4>{{ connection.target.title }}</h4> <h4 class="font-semibold mb-1">{{ connection.target.title }}</h4>
<p>{{ connection.target.excerpt }}</p> <p class="text-sm text-gray-400">{{ connection.target.excerpt }}</p>
</div> </div>
</div> </div>
<div class="connection-meta"> <div class="connection-meta mt-2 text-right">
<span>{{ connection.date }}</span> <span class="text-xs text-gray-500">{{ connection.date }}</span>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div>
{% else %} {% else %}
<div class="text-center py-12"> <div class="text-center py-12">
<i class="fas fa-project-diagram text-5xl text-gray-400 mb-4"></i> <i class="fas fa-project-diagram text-5xl text-gray-400 mb-4"></i>
@@ -590,54 +633,50 @@
<div class="tab-content hidden" id="settings-tab"> <div class="tab-content hidden" id="settings-tab">
<!-- Einstellungs-Tab-Inhalt --> <!-- Einstellungs-Tab-Inhalt -->
<div class="settings-card"> <div class="settings-card bg-white/10 backdrop-blur-md rounded-xl p-6 mb-6 border border-white/10">
<div class="settings-card-header">Profilinformationen</div> <div class="settings-card-header text-xl font-bold mb-4 text-purple-300">Profilinformationen</div>
<div class="settings-card-body"> <div class="settings-card-body">
<div class="settings-group"> <div class="settings-group mb-4">
<label class="settings-label" for="name">Name</label> <label class="settings-label block text-gray-300 mb-2" for="bio">Über mich</label>
<input type="text" id="name" class="settings-input" value="{{ user.name }}" /> <textarea id="bio" class="settings-input w-full bg-gray-800/60 border border-gray-700/60 text-white rounded-lg p-3 focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all" rows="4">{{ user.bio }}</textarea>
</div> </div>
<div class="settings-group"> <div class="settings-group mb-4">
<label class="settings-label" for="bio">Über mich</label> <label class="settings-label block text-gray-300 mb-2" for="location">Standort</label>
<textarea id="bio" class="settings-input" rows="4">{{ user.bio }}</textarea> <input type="text" id="location" class="settings-input w-full bg-gray-800/60 border border-gray-700/60 text-white rounded-lg p-3 focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all" value="{{ user.location }}" />
</div> </div>
<div class="settings-group"> <div class="settings-group mb-4">
<label class="settings-label" for="location">Standort</label> <label class="settings-label block text-gray-300 mb-2" for="website">Website</label>
<input type="text" id="location" class="settings-input" value="{{ user.location }}" /> <input type="url" id="website" class="settings-input w-full bg-gray-800/60 border border-gray-700/60 text-white rounded-lg p-3 focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all" value="{{ user.website }}" />
</div> </div>
<div class="settings-group"> <button id="save-profile-btn" class="profile-action-btn primary mt-4">
<label class="settings-label" for="website">Website</label>
<input type="url" id="website" class="settings-input" value="{{ user.website }}" />
</div>
<button class="profile-action-btn primary mt-4">
<i class="fas fa-save mr-1"></i> Speichern <i class="fas fa-save mr-1"></i> Speichern
</button> </button>
</div> </div>
</div> </div>
<div class="settings-card"> <div class="settings-card bg-white/10 backdrop-blur-md rounded-xl p-6 mb-6 border border-white/10">
<div class="settings-card-header">Datenschutz und Sicherheit</div> <div class="settings-card-header text-xl font-bold mb-4 text-purple-300">Datenschutz und Sicherheit</div>
<div class="settings-card-body"> <div class="settings-card-body">
<div class="settings-group"> <div class="settings-group mb-4">
<label class="settings-label" for="email">E-Mail-Adresse</label> <label class="settings-label block text-gray-300 mb-2" for="email">E-Mail-Adresse</label>
<input type="email" id="email" class="settings-input" value="{{ user.email }}" /> <input type="email" id="email" class="settings-input w-full bg-gray-800/60 border border-gray-700/60 text-white rounded-lg p-3 focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all" value="{{ user.email }}" readonly />
<p class="text-xs text-gray-500 mt-1">Die E-Mail-Adresse kann derzeit nicht geändert werden.</p>
</div> </div>
<div class="settings-group"> <div class="settings-group mb-4">
<label class="settings-label" for="password">Neues Passwort</label> <label class="settings-label block text-gray-300 mb-2" for="password">Neues Passwort</label>
<input type="password" id="password" class="settings-input" placeholder="Neues Passwort eingeben" /> <input type="password" id="password" class="settings-input w-full bg-gray-800/60 border border-gray-700/60 text-white rounded-lg p-3 focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all" placeholder="Neues Passwort eingeben" />
</div> </div>
<div class="settings-group"> <div class="settings-group mb-4">
<label class="settings-label" for="password_confirm">Passwort bestätigen</label> <label class="settings-label block text-gray-300 mb-2" for="password_confirm">Passwort bestätigen</label>
<input type="password" id="password_confirm" class="settings-input" placeholder="Passwort wiederholen" /> <input type="password" id="password_confirm" class="settings-input w-full bg-gray-800/60 border border-gray-700/60 text-white rounded-lg p-3 focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all" placeholder="Passwort wiederholen" />
</div> </div>
<button class="profile-action-btn primary mt-4"> <button id="update-password-btn" class="profile-action-btn primary mt-4">
<i class="fas fa-lock mr-1"></i> Passwort aktualisieren <i class="fas fa-lock mr-1"></i> Passwort aktualisieren
</button> </button>
</div> </div>
@@ -655,6 +694,12 @@
const tabs = document.querySelectorAll('.profile-tab'); const tabs = document.querySelectorAll('.profile-tab');
const tabContents = document.querySelectorAll('.tab-content'); const tabContents = document.querySelectorAll('.tab-content');
// Standardmäßig den ersten Tab aktivieren
if (tabs.length > 0 && tabContents.length > 0) {
tabs[0].classList.add('active');
tabContents[0].classList.remove('hidden');
}
tabs.forEach(tab => { tabs.forEach(tab => {
tab.addEventListener('click', function() { tab.addEventListener('click', function() {
// Alle Tabs deaktivieren // Alle Tabs deaktivieren
@@ -706,6 +751,8 @@
} }
countElement.textContent = count; countElement.textContent = count;
// Hier könnte ein AJAX-Request erfolgen, um die Reaktion zu speichern
}); });
}); });
@@ -726,7 +773,7 @@
const avatarEditBtn = document.querySelector('.avatar-edit'); const avatarEditBtn = document.querySelector('.avatar-edit');
if (avatarEditBtn) { if (avatarEditBtn) {
avatarEditBtn.addEventListener('click', function() { avatarEditBtn.addEventListener('click', function() {
// Dateiauwahl öffnen // Dateiauswahl öffnen
const fileInput = document.createElement('input'); const fileInput = document.createElement('input');
fileInput.type = 'file'; fileInput.type = 'file';
fileInput.accept = 'image/*'; fileInput.accept = 'image/*';
@@ -738,13 +785,27 @@
fileInput.addEventListener('change', function() { fileInput.addEventListener('change', function() {
if (this.files && this.files[0]) { if (this.files && this.files[0]) {
// Anzeigen des gewählten Bildes // Anzeigen des gewählten Bildes
const avatarImg = document.querySelector('.avatar'); const avatarImg = document.querySelector('.avatar') || document.querySelector('.default-avatar');
// FileReader zum Einlesen des Bildes // FileReader zum Einlesen des Bildes
const reader = new FileReader(); const reader = new FileReader();
reader.onload = function(e) { reader.onload = function(e) {
// Vorschau anzeigen // Vorschau anzeigen
if (avatarImg.tagName.toLowerCase() === 'img') {
avatarImg.src = e.target.result; avatarImg.src = e.target.result;
} else {
// Falls es ein div mit SVG ist, ersetzen wir es durch ein Image
const imgElement = document.createElement('img');
imgElement.src = e.target.result;
imgElement.classList.add('avatar');
imgElement.alt = "Profilbild";
const avatarContainer = document.querySelector('.avatar-container');
if (avatarContainer) {
avatarContainer.innerHTML = '';
avatarContainer.appendChild(imgElement);
}
}
// Avatar-URL im Einstellungsbereich speichern // Avatar-URL im Einstellungsbereich speichern
const avatarUrlInput = document.createElement('input'); const avatarUrlInput = document.createElement('input');
@@ -778,59 +839,11 @@
}); });
} }
// Einstellungen-Formular-Handling // Profil Speichern-Button
const saveSettingsBtn = document.querySelectorAll('.settings-card .profile-action-btn.primary'); const saveProfileBtn = document.getElementById('save-profile-btn');
saveSettingsBtn.forEach(btn => { if (saveProfileBtn) {
btn.addEventListener('click', function() { saveProfileBtn.addEventListener('click', function() {
const isPasswordUpdate = this.textContent.includes('Passwort');
// Passwort-Update
if (isPasswordUpdate) {
const currentPassword = document.getElementById('password').value;
const newPassword = document.getElementById('password_confirm').value;
if (!currentPassword || !newPassword) {
showNotification('Bitte fülle alle Passwortfelder aus', 'error');
return;
}
// AJAX-Anfrage senden
const formData = new FormData();
formData.append('action', 'update_password');
formData.append('current_password', currentPassword);
formData.append('new_password', newPassword);
formData.append('confirm_password', newPassword);
// Visuelle Rückmeldung
const originalText = this.innerHTML;
this.innerHTML = '<i class="fas fa-circle-notch fa-spin mr-1"></i> Speichern...';
this.disabled = true;
fetch('{{ url_for("settings") }}', {
method: 'POST',
body: formData
})
.then(response => response.json())
.catch(error => {
console.error('Fehler beim Aktualisieren des Passworts:', error);
return { success: false, message: 'Netzwerkfehler. Bitte versuche es erneut.' };
})
.then(data => {
this.innerHTML = originalText;
this.disabled = false;
if (data && data.success) {
showNotification('Passwort erfolgreich aktualisiert!', 'success');
document.getElementById('password').value = '';
document.getElementById('password_confirm').value = '';
} else {
showNotification(data?.message || 'Fehler beim Aktualisieren des Passworts', 'error');
}
});
}
// Profil-Update
else {
// Sammle Daten aus den Eingabefeldern // Sammle Daten aus den Eingabefeldern
const formData = new FormData(); const formData = new FormData();
formData.append('action', 'update_profile'); formData.append('action', 'update_profile');
@@ -888,9 +901,61 @@
showNotification(data?.message || 'Fehler beim Aktualisieren des Profils', 'error'); showNotification(data?.message || 'Fehler beim Aktualisieren des Profils', 'error');
} }
}); });
});
}
// Passwort Update Button
const updatePasswordBtn = document.getElementById('update-password-btn');
if (updatePasswordBtn) {
updatePasswordBtn.addEventListener('click', function() {
const currentPassword = document.getElementById('password').value;
const newPassword = document.getElementById('password_confirm').value;
if (!currentPassword || !newPassword) {
showNotification('Bitte fülle alle Passwortfelder aus', 'error');
return;
}
if (currentPassword !== newPassword) {
showNotification('Die Passwörter stimmen nicht überein', 'error');
return;
}
// AJAX-Anfrage senden
const formData = new FormData();
formData.append('action', 'update_password');
formData.append('new_password', newPassword);
formData.append('confirm_password', newPassword);
// Visuelle Rückmeldung
const originalText = this.innerHTML;
this.innerHTML = '<i class="fas fa-circle-notch fa-spin mr-1"></i> Aktualisieren...';
this.disabled = true;
fetch('{{ url_for("settings") }}', {
method: 'POST',
body: formData
})
.then(response => response.json())
.catch(error => {
console.error('Fehler beim Aktualisieren des Passworts:', error);
return { success: false, message: 'Netzwerkfehler. Bitte versuche es erneut.' };
})
.then(data => {
this.innerHTML = originalText;
this.disabled = false;
if (data && data.success) {
showNotification('Passwort erfolgreich aktualisiert!', 'success');
document.getElementById('password').value = '';
document.getElementById('password_confirm').value = '';
} else {
showNotification(data?.message || 'Fehler beim Aktualisieren des Passworts', 'error');
} }
}); });
}); });
}
// Gedanken-Karten mit Hover-Effekten und Border-Farben // Gedanken-Karten mit Hover-Effekten und Border-Farben
const thoughtItems = document.querySelectorAll('.thought-item'); const thoughtItems = document.querySelectorAll('.thought-item');
@@ -909,7 +974,7 @@
// Border-Farben anwenden // Border-Farben anwenden
const borderElem = item.querySelector('.thought-border'); const borderElem = item.querySelector('.thought-border');
if (borderElem && borderElem.dataset.color) { if (borderElem && borderElem.dataset.color) {
borderElem.style.borderLeftColor = borderElem.dataset.color; borderElem.style.borderLeft = `4px solid ${borderElem.dataset.color}`;
} }
}); });