feat(profile): verbessere das Profil-Layout mit neuen Tab-Stilen, verbessere die Benutzeroberfläche für Profil- und Passwortaktualisierungen und füge Animationen für Tab-Inhalte hinzu.

This commit is contained in:
2025-05-10 15:56:14 +02:00
parent d99cae4956
commit 44c7183e97

View File

@@ -250,6 +250,36 @@
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 */
.stat-item {
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-calendar-alt"></i> Mitglied seit {{ user.created_at.strftime('%d.%m.%Y') }}</span>
</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>
@@ -534,16 +566,25 @@
<div class="tab-content hidden" id="collections-tab">
<div id="collections-container">
{% if collections %}
{% for collection in collections %}
<div class="collection-item">
<h3>{{ collection.title }}</h3>
<p>{{ collection.description }}</p>
<div class="collection-meta">
<span>{{ collection.thoughts_count }} Gedanken</span>
<span>{{ collection.date }}</span>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for collection in collections %}
<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">
<div class="p-5">
<h3 class="text-xl font-bold mb-2 text-purple-400">{{ collection.title }}</h3>
<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.date }}</span>
</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>
</div>
{% endfor %}
{% endfor %}
</div>
{% else %}
<div class="text-center py-12">
<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 id="connections-container">
{% if connections %}
{% for connection in connections %}
<div class="connection-item">
<div class="connection-nodes">
<div class="connection-node">
<h4>{{ connection.source.title }}</h4>
<p>{{ connection.source.excerpt }}</p>
<div class="grid grid-cols-1 gap-6">
{% for connection in connections %}
<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 flex items-stretch">
<div class="connection-node flex-1 p-4 rounded-lg bg-gray-900/80 text-white">
<h4 class="font-semibold mb-1">{{ connection.source.title }}</h4>
<p class="text-sm text-gray-400">{{ connection.source.excerpt }}</p>
</div>
<div class="connection-type flex flex-col items-center justify-center px-4">
<i class="fas fa-arrow-right text-purple-400 mb-1"></i>
<span class="text-xs text-gray-500">{{ connection.relation_type }}</span>
</div>
<div class="connection-node flex-1 p-4 rounded-lg bg-gray-900/80 text-white">
<h4 class="font-semibold mb-1">{{ connection.target.title }}</h4>
<p class="text-sm text-gray-400">{{ connection.target.excerpt }}</p>
</div>
</div>
<div class="connection-type">
<i class="fas fa-arrow-right"></i>
<span>{{ connection.relation_type }}</span>
</div>
<div class="connection-node">
<h4>{{ connection.target.title }}</h4>
<p>{{ connection.target.excerpt }}</p>
<div class="connection-meta mt-2 text-right">
<span class="text-xs text-gray-500">{{ connection.date }}</span>
</div>
</div>
<div class="connection-meta">
<span>{{ connection.date }}</span>
</div>
</div>
{% endfor %}
{% endfor %}
</div>
{% else %}
<div class="text-center py-12">
<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">
<!-- Einstellungs-Tab-Inhalt -->
<div class="settings-card">
<div class="settings-card-header">Profilinformationen</div>
<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 text-xl font-bold mb-4 text-purple-300">Profilinformationen</div>
<div class="settings-card-body">
<div class="settings-group">
<label class="settings-label" for="name">Name</label>
<input type="text" id="name" class="settings-input" value="{{ user.name }}" />
<div class="settings-group mb-4">
<label class="settings-label block text-gray-300 mb-2" for="bio">Über mich</label>
<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 class="settings-group">
<label class="settings-label" for="bio">Über mich</label>
<textarea id="bio" class="settings-input" rows="4">{{ user.bio }}</textarea>
<div class="settings-group mb-4">
<label class="settings-label block text-gray-300 mb-2" for="location">Standort</label>
<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 class="settings-group">
<label class="settings-label" for="location">Standort</label>
<input type="text" id="location" class="settings-input" value="{{ user.location }}" />
<div class="settings-group mb-4">
<label class="settings-label block text-gray-300 mb-2" for="website">Website</label>
<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 class="settings-group">
<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">
<button id="save-profile-btn" class="profile-action-btn primary mt-4">
<i class="fas fa-save mr-1"></i> Speichern
</button>
</div>
</div>
<div class="settings-card">
<div class="settings-card-header">Datenschutz und Sicherheit</div>
<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 text-xl font-bold mb-4 text-purple-300">Datenschutz und Sicherheit</div>
<div class="settings-card-body">
<div class="settings-group">
<label class="settings-label" for="email">E-Mail-Adresse</label>
<input type="email" id="email" class="settings-input" value="{{ user.email }}" />
<div class="settings-group mb-4">
<label class="settings-label block text-gray-300 mb-2" for="email">E-Mail-Adresse</label>
<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 class="settings-group">
<label class="settings-label" for="password">Neues Passwort</label>
<input type="password" id="password" class="settings-input" placeholder="Neues Passwort eingeben" />
<div class="settings-group mb-4">
<label class="settings-label block text-gray-300 mb-2" for="password">Neues Passwort</label>
<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 class="settings-group">
<label class="settings-label" for="password_confirm">Passwort bestätigen</label>
<input type="password" id="password_confirm" class="settings-input" placeholder="Passwort wiederholen" />
<div class="settings-group mb-4">
<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 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>
<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
</button>
</div>
@@ -655,6 +694,12 @@
const tabs = document.querySelectorAll('.profile-tab');
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 => {
tab.addEventListener('click', function() {
// Alle Tabs deaktivieren
@@ -706,6 +751,8 @@
}
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');
if (avatarEditBtn) {
avatarEditBtn.addEventListener('click', function() {
// Dateiauwahl öffnen
// Dateiauswahl öffnen
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
@@ -738,13 +785,27 @@
fileInput.addEventListener('change', function() {
if (this.files && this.files[0]) {
// Anzeigen des gewählten Bildes
const avatarImg = document.querySelector('.avatar');
const avatarImg = document.querySelector('.avatar') || document.querySelector('.default-avatar');
// FileReader zum Einlesen des Bildes
const reader = new FileReader();
reader.onload = function(e) {
// Vorschau anzeigen
avatarImg.src = e.target.result;
if (avatarImg.tagName.toLowerCase() === 'img') {
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
const avatarUrlInput = document.createElement('input');
@@ -778,119 +839,123 @@
});
}
// Einstellungen-Formular-Handling
const saveSettingsBtn = document.querySelectorAll('.settings-card .profile-action-btn.primary');
// Profil Speichern-Button
const saveProfileBtn = document.getElementById('save-profile-btn');
saveSettingsBtn.forEach(btn => {
btn.addEventListener('click', function() {
const isPasswordUpdate = this.textContent.includes('Passwort');
if (saveProfileBtn) {
saveProfileBtn.addEventListener('click', function() {
// Sammle Daten aus den Eingabefeldern
const formData = new FormData();
formData.append('action', 'update_profile');
formData.append('bio', document.getElementById('bio').value || '');
formData.append('location', document.getElementById('location').value || '');
formData.append('website', document.getElementById('website').value || '');
// 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
const formData = new FormData();
formData.append('action', 'update_profile');
formData.append('bio', document.getElementById('bio').value || '');
formData.append('location', document.getElementById('location').value || '');
formData.append('website', document.getElementById('website').value || '');
// Avatar hinzufügen, falls vorhanden
const avatarUrlInput = document.getElementById('avatar_url');
if (avatarUrlInput) {
formData.append('avatar_url', avatarUrlInput.value);
}
// Visuelle Rückmeldung
const originalText = this.innerHTML;
this.innerHTML = '<i class="fas fa-circle-notch fa-spin mr-1"></i> Speichern...';
this.disabled = true;
// AJAX-Anfrage senden
fetch('{{ url_for("settings") }}', {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error('Fehler beim Speichern');
}
return response.json();
})
.catch(error => {
console.error('Fehler beim Speichern der Profileinstellungen:', error);
return { success: false, message: 'Netzwerkfehler. Bitte versuche es erneut.' };
})
.then(data => {
this.innerHTML = originalText;
this.disabled = false;
if (data && data.success) {
// Erfolgsanimation
showNotification('Profil erfolgreich aktualisiert!', 'success');
// UI aktualisieren ohne Neuladen
const bioElement = document.querySelector('.user-bio');
const locationElement = document.querySelector('.user-meta span:first-child');
if (bioElement) {
bioElement.textContent = document.getElementById('bio').value || 'Keine Bio vorhanden. Klicke auf bearbeiten, um eine hinzuzufügen.';
}
if (locationElement) {
const location = document.getElementById('location').value;
locationElement.innerHTML = `<i class="fas fa-map-marker-alt"></i> ${location || 'Kein Standort angegeben'}`;
}
} else {
showNotification(data?.message || 'Fehler beim Aktualisieren des Profils', 'error');
}
});
// Avatar hinzufügen, falls vorhanden
const avatarUrlInput = document.getElementById('avatar_url');
if (avatarUrlInput) {
formData.append('avatar_url', avatarUrlInput.value);
}
// Visuelle Rückmeldung
const originalText = this.innerHTML;
this.innerHTML = '<i class="fas fa-circle-notch fa-spin mr-1"></i> Speichern...';
this.disabled = true;
// AJAX-Anfrage senden
fetch('{{ url_for("settings") }}', {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error('Fehler beim Speichern');
}
return response.json();
})
.catch(error => {
console.error('Fehler beim Speichern der Profileinstellungen:', error);
return { success: false, message: 'Netzwerkfehler. Bitte versuche es erneut.' };
})
.then(data => {
this.innerHTML = originalText;
this.disabled = false;
if (data && data.success) {
// Erfolgsanimation
showNotification('Profil erfolgreich aktualisiert!', 'success');
// UI aktualisieren ohne Neuladen
const bioElement = document.querySelector('.user-bio');
const locationElement = document.querySelector('.user-meta span:first-child');
if (bioElement) {
bioElement.textContent = document.getElementById('bio').value || 'Keine Bio vorhanden. Klicke auf bearbeiten, um eine hinzuzufügen.';
}
if (locationElement) {
const location = document.getElementById('location').value;
locationElement.innerHTML = `<i class="fas fa-map-marker-alt"></i> ${location || 'Kein Standort angegeben'}`;
}
} else {
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
const thoughtItems = document.querySelectorAll('.thought-item');
@@ -909,7 +974,7 @@
// Border-Farben anwenden
const borderElem = item.querySelector('.thought-border');
if (borderElem && borderElem.dataset.color) {
borderElem.style.borderLeftColor = borderElem.dataset.color;
borderElem.style.borderLeft = `4px solid ${borderElem.dataset.color}`;
}
});