Profil-Seite: Anzeigename, Passwort ändern, Persona (ersetzt USER.md)
- Neuer Menüpunkt 'Mein Profil' statt 'Logout' - Profil-Page: Anzeigename, Persona (Freitext), Passwort ändern, Logout - ProfileEndpoint: GET/PUT /api/profile, PUT /api/profile/password - User-Persona im System-Prompt statt USER.md (Fallback: Anzeigename/Username) - SystemPromptBuilder: dataDir-Parameter entfernt, userPersona-Parameter hinzugefügt - DB-Migration: persona-Spalte in users-Tabelle
This commit is contained in:
1 parent
f49c526384
commit
7149904ae4
12 files changed
+341
-26
No files matched your search
@@ -82,9 +82,9 @@ public static class ChatEndpoint
|
||||
|
||||
var markedMessages = await db.GetMarkedMessages(sessionId);
|
||||
var agentMemoryDir = memoryService.GetAgentMemoryDir(agent.AgentId);
|
||||
var dataDir = memoryService.DataDir;
|
||||
var agentSkills = await db.GetAgentSkills(agent.AgentId);
|
||||
var systemPromptText = SystemPromptBuilder.Build(agent, session, markedMessages, memoryService, agentMemoryDir, dataDir, agentSkills, skillService);
|
||||
var user = await db.GetUser(userId);
|
||||
var systemPromptText = SystemPromptBuilder.Build(agent, session, markedMessages, memoryService, agentMemoryDir, agentSkills, skillService, userPersona: user?.GetEffectivePersona());
|
||||
|
||||
var chainId = Guid.NewGuid().ToString("N");
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
using System.Text.Json;
|
||||
using Gott.Extensions;
|
||||
using Gott.Services;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Gott.Endpoints;
|
||||
|
||||
public static class ProfileEndpoint
|
||||
{
|
||||
public static void MapProfileEndpoints(this WebApplication app)
|
||||
{
|
||||
app.MapGet("/api/profile", HandleGet);
|
||||
app.MapPut("/api/profile", HandleUpdate);
|
||||
app.MapPut("/api/profile/password", HandlePasswordChange);
|
||||
}
|
||||
|
||||
private static async System.Threading.Tasks.Task HandleGet(HttpContext ctx, DatabaseService db)
|
||||
{
|
||||
var userId = ctx.GetUserId();
|
||||
var user = await db.GetUser(userId);
|
||||
if (user == null)
|
||||
{
|
||||
ctx.Response.StatusCode = 404;
|
||||
await ctx.Response.WriteAsJsonAsync(new { error = "User not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.Response.WriteAsJsonAsync(new
|
||||
{
|
||||
userId = user.UserId,
|
||||
username = user.Username,
|
||||
displayName = user.DisplayName,
|
||||
persona = user.Persona,
|
||||
isAdmin = user.IsAdmin
|
||||
});
|
||||
}
|
||||
|
||||
private static async System.Threading.Tasks.Task HandleUpdate(HttpContext ctx, DatabaseService db)
|
||||
{
|
||||
var userId = ctx.GetUserId();
|
||||
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
|
||||
|
||||
var displayName = body.TryGetProperty("displayName", out var dn) ? dn.GetString() : null;
|
||||
var persona = body.TryGetProperty("persona", out var p) ? p.GetString() : null;
|
||||
|
||||
await db.UpdateUserProfile(userId, displayName, persona);
|
||||
|
||||
var user = await db.GetUser(userId);
|
||||
await ctx.Response.WriteAsJsonAsync(new
|
||||
{
|
||||
userId = user.UserId,
|
||||
username = user.Username,
|
||||
displayName = user.DisplayName,
|
||||
persona = user.Persona,
|
||||
isAdmin = user.IsAdmin
|
||||
});
|
||||
}
|
||||
|
||||
private static async System.Threading.Tasks.Task HandlePasswordChange(HttpContext ctx, DatabaseService db, AuthService auth)
|
||||
{
|
||||
var userId = ctx.GetUserId();
|
||||
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
|
||||
|
||||
var currentPassword = body.TryGetProperty("currentPassword", out var cp) ? cp.GetString() : null;
|
||||
var newPassword = body.TryGetProperty("newPassword", out var np) ? np.GetString() : null;
|
||||
|
||||
if (string.IsNullOrEmpty(currentPassword) || string.IsNullOrEmpty(newPassword))
|
||||
{
|
||||
ctx.Response.StatusCode = 400;
|
||||
await ctx.Response.WriteAsJsonAsync(new { error = "Aktuelles und neues Passwort erforderlich" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword.Length < 4)
|
||||
{
|
||||
ctx.Response.StatusCode = 400;
|
||||
await ctx.Response.WriteAsJsonAsync(new { error = "Passwort muss mindestens 4 Zeichen lang sein" });
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await db.GetUser(userId);
|
||||
if (user == null || !auth.VerifyPassword(currentPassword, user.PasswordHash))
|
||||
{
|
||||
ctx.Response.StatusCode = 401;
|
||||
await ctx.Response.WriteAsJsonAsync(new { error = "Aktuelles Passwort ist falsch" });
|
||||
return;
|
||||
}
|
||||
|
||||
var newHash = auth.HashPassword(newPassword);
|
||||
await db.UpdateUserPassword(userId, newHash);
|
||||
|
||||
await ctx.Response.WriteAsJsonAsync(new { ok = true });
|
||||
}
|
||||
}
|
||||
@@ -139,9 +139,9 @@ public static class SessionEndpoint
|
||||
|
||||
var markedMessages = await db.GetMarkedMessages(sessionId);
|
||||
var agentMemoryDir = memoryService.GetAgentMemoryDir(agent.AgentId);
|
||||
var dataDir = memoryService.DataDir;
|
||||
var agentSkills = await db.GetAgentSkills(agent.AgentId);
|
||||
var systemPrompt = SystemPromptBuilder.Build(agent, session, markedMessages, memoryService, agentMemoryDir, dataDir, agentSkills, skillService);
|
||||
var user = await db.GetUser(userId);
|
||||
var systemPrompt = SystemPromptBuilder.Build(agent, session, markedMessages, memoryService, agentMemoryDir, agentSkills, skillService, userPersona: user?.GetEffectivePersona());
|
||||
|
||||
ctx.Response.ContentType = "text/plain; charset=utf-8";
|
||||
await ctx.Response.WriteAsync(systemPrompt);
|
||||
@@ -173,9 +173,9 @@ public static class SessionEndpoint
|
||||
|
||||
var markedMessages = await db.GetMarkedMessages(sessionId);
|
||||
var agentMemoryDir = memoryService.GetAgentMemoryDir(agent.AgentId);
|
||||
var dataDir = memoryService.DataDir;
|
||||
var agentSkills = await db.GetAgentSkills(agent.AgentId);
|
||||
var blocks = SystemPromptBuilder.BuildBlocks(agent, session, markedMessages, memoryService, agentMemoryDir, dataDir, agentSkills, skillService);
|
||||
var user = await db.GetUser(userId);
|
||||
var blocks = SystemPromptBuilder.BuildBlocks(agent, session, markedMessages, memoryService, agentMemoryDir, agentSkills, skillService, userPersona: user?.GetEffectivePersona());
|
||||
|
||||
await ctx.Response.WriteAsJsonAsync(blocks.Select(b => new { b.Icon, b.Label, b.Content }));
|
||||
}
|
||||
|
||||
@@ -10,4 +10,14 @@ public class UserRecord
|
||||
public string CreatedAt { get; set; }
|
||||
public string UpdatedAt { get; set; }
|
||||
public bool IsAdmin { get; set; }
|
||||
public string Persona { get; set; }
|
||||
|
||||
public string GetEffectivePersona()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(Persona))
|
||||
return Persona;
|
||||
|
||||
var name = !string.IsNullOrWhiteSpace(DisplayName) ? DisplayName : Username;
|
||||
return $"Mein Name ist **{name}**.";
|
||||
}
|
||||
}
|
||||
@@ -37,5 +37,6 @@ app.MapWorkspaceEndpoint();
|
||||
app.MapInfoEndpoint();
|
||||
app.MapBackupEndpoint();
|
||||
app.MapSkillEndpoints();
|
||||
app.MapProfileEndpoints();
|
||||
|
||||
app.Run("http://0.0.0.0:19000");
|
||||
@@ -159,6 +159,7 @@ public class DatabaseService
|
||||
TryAddColumn(conn, "sessions", "user_id", "TEXT");
|
||||
TryAddColumn(conn, "agents", "user_id", "TEXT");
|
||||
TryAddColumn(conn, "users", "is_admin", "INTEGER NOT NULL DEFAULT 0");
|
||||
TryAddColumn(conn, "users", "persona", "TEXT");
|
||||
|
||||
conn.Execute(@"
|
||||
CREATE TABLE IF NOT EXISTS agent_skills (
|
||||
@@ -733,4 +734,12 @@ public class DatabaseService
|
||||
"UPDATE users SET password_hash = @passwordHash, updated_at = @now WHERE user_id = @userId",
|
||||
new { userId, passwordHash, now = DateTime.UtcNow.ToString("o") });
|
||||
}
|
||||
|
||||
public async Task UpdateUserProfile(string userId, string displayName, string persona)
|
||||
{
|
||||
using var conn = CreateConnection();
|
||||
await conn.ExecuteAsync(
|
||||
"UPDATE users SET display_name = @displayName, persona = @persona, updated_at = @now WHERE user_id = @userId",
|
||||
new { userId, displayName, persona, now = DateTime.UtcNow.ToString("o") });
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public class PromptBlock
|
||||
|
||||
public static class SystemPromptBuilder
|
||||
{
|
||||
public static List<PromptBlock> BuildBlocks(AgentRecord agent, SessionRecord session, IEnumerable<MessageRecord> markedMessages = null, MemoryService memoryService = null, string agentMemoryDir = null, string dataDir = null, IEnumerable<string> skillSlugs = null, SkillService skillService = null)
|
||||
public static List<PromptBlock> BuildBlocks(AgentRecord agent, SessionRecord session, IEnumerable<MessageRecord> markedMessages = null, MemoryService memoryService = null, string agentMemoryDir = null, IEnumerable<string> skillSlugs = null, SkillService skillService = null, string userPersona = null)
|
||||
{
|
||||
var blocks = new List<PromptBlock>();
|
||||
|
||||
@@ -39,19 +39,10 @@ public static class SystemPromptBuilder
|
||||
blocks.Add(new PromptBlock { Icon = "fa-heart", Label = "Agent · SOUL.md (UseSoul)", Content = soul });
|
||||
}
|
||||
|
||||
// USER.md
|
||||
if (agent.UseUserInfo && !string.IsNullOrWhiteSpace(dataDir))
|
||||
// User-Info: Persona aus dem Profil des Users der gerade schreibt
|
||||
if (agent.UseUserInfo && !string.IsNullOrWhiteSpace(userPersona))
|
||||
{
|
||||
var userFile = System.IO.Path.Combine(dataDir, "USER.md");
|
||||
if (System.IO.File.Exists(userFile))
|
||||
{
|
||||
var userContent = System.IO.File.ReadAllText(userFile);
|
||||
if (!string.IsNullOrWhiteSpace(userContent))
|
||||
{
|
||||
var absolutePath = System.IO.Path.GetFullPath(userFile);
|
||||
blocks.Add(new PromptBlock { Icon = "fa-user", Label = "Agent · USER.md (UseUserInfo)", Content = $"{userContent}\n\n_Pfad: `{absolutePath}`_" });
|
||||
}
|
||||
}
|
||||
blocks.Add(new PromptBlock { Icon = "fa-user", Label = "User · Persona (UseUserInfo)", Content = $"## Dein Gegenüber\nDie folgende Beschreibung stammt von der Person, die dir gerade schreibt:\n\n{userPersona}" });
|
||||
}
|
||||
|
||||
bool toolAny = string.Equals(agent.ToolChoice, "any", System.StringComparison.OrdinalIgnoreCase);
|
||||
@@ -233,9 +224,9 @@ Bei Zeilenreferenzen (z.B. `[Datei: foo.txt:11]`) IMMER `cat -n` verwenden um di
|
||||
return blocks;
|
||||
}
|
||||
|
||||
public static string Build(AgentRecord agent, SessionRecord session, IEnumerable<MessageRecord> markedMessages = null, MemoryService memoryService = null, string agentMemoryDir = null, string dataDir = null, IEnumerable<string> skillSlugs = null, SkillService skillService = null)
|
||||
public static string Build(AgentRecord agent, SessionRecord session, IEnumerable<MessageRecord> markedMessages = null, MemoryService memoryService = null, string agentMemoryDir = null, IEnumerable<string> skillSlugs = null, SkillService skillService = null, string userPersona = null)
|
||||
{
|
||||
var blocks = BuildBlocks(agent, session, markedMessages, memoryService, agentMemoryDir, dataDir, skillSlugs, skillService);
|
||||
var blocks = BuildBlocks(agent, session, markedMessages, memoryService, agentMemoryDir, skillSlugs, skillService, userPersona);
|
||||
return string.Join("\n\n", blocks.Select(b => b.Content));
|
||||
}
|
||||
}
|
||||
@@ -195,6 +195,30 @@ label.skill-item {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* ── Profile ── */
|
||||
|
||||
.profile-message {
|
||||
padding: var(--badge-spacing) var(--box-spacing);
|
||||
border-radius: var(--border-radius);
|
||||
margin-top: var(--badge-spacing);
|
||||
}
|
||||
|
||||
.profile-message-success {
|
||||
background: rgba(0, 180, 0, 0.15);
|
||||
color: #0a0;
|
||||
}
|
||||
|
||||
.profile-message-error {
|
||||
background: rgba(220, 0, 0, 0.15);
|
||||
color: var(--dangerous);
|
||||
}
|
||||
|
||||
.profile-divider {
|
||||
border: none;
|
||||
border-top: var(--border-width) solid var(--f2-border);
|
||||
margin: var(--box-spacing) 0;
|
||||
}
|
||||
|
||||
.danger-btn {
|
||||
color: var(--dangerous);
|
||||
border-color: var(--dangerous);
|
||||
|
||||
+40
-1
@@ -50,7 +50,7 @@
|
||||
<h1 class="dashboard-title">// gott</h1>
|
||||
<div class="dashboard-links">
|
||||
<a id="dashboardSettingsLink" class="dashboard-link">Einstellungen</a>
|
||||
<a id="dashboardLogoutLink" class="dashboard-link">Logout</a>
|
||||
<a id="dashboardProfileLink" class="dashboard-link">Mein Profil</a>
|
||||
</div>
|
||||
</header>
|
||||
<div class="page-content">
|
||||
@@ -288,6 +288,44 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="tpl-profile">
|
||||
<div class="page form-page">
|
||||
<header>
|
||||
<button class="back-btn icon-btn" data-page="dashboard"><i class="fa fa-arrow-left"></i></button>
|
||||
<h1>Mein Profil</h1>
|
||||
</header>
|
||||
<div class="page-content">
|
||||
<div class="form">
|
||||
<label>Benutzername</label>
|
||||
<input type="text" id="profileUsername" disabled>
|
||||
<label>Anzeigename</label>
|
||||
<input type="text" id="profileDisplayName" placeholder="Anzeigename">
|
||||
<label>Persona</label>
|
||||
<span class="setting-hint">Wird im System-Prompt verwendet, wenn der Agent "Kennt sein Gegenüber" aktiviert hat. Ersetzt USER.md.</span>
|
||||
<textarea id="profilePersona" rows="8" placeholder="Beschreibe dich selbst – Name, Rolle, Präferenzen, Kommunikationsstil..."></textarea>
|
||||
<div class="form-actions">
|
||||
<button id="profileSaveBtn">Speichern</button>
|
||||
</div>
|
||||
<div class="profile-message" id="profileMessage" hidden></div>
|
||||
<hr class="profile-divider">
|
||||
<h2>Passwort ändern</h2>
|
||||
<label>Aktuelles Passwort</label>
|
||||
<input type="password" id="profileCurrentPassword" placeholder="Aktuelles Passwort" autocomplete="current-password">
|
||||
<label>Neues Passwort</label>
|
||||
<input type="password" id="profileNewPassword" placeholder="Neues Passwort" autocomplete="new-password">
|
||||
<label>Neues Passwort bestätigen</label>
|
||||
<input type="password" id="profileConfirmPassword" placeholder="Passwort wiederholen" autocomplete="new-password">
|
||||
<div class="form-actions">
|
||||
<button id="profilePasswordBtn">Passwort ändern</button>
|
||||
</div>
|
||||
<div class="profile-message" id="profilePasswordMessage" hidden></div>
|
||||
<hr class="profile-divider">
|
||||
<button id="profileLogoutBtn" class="danger-btn">Abmelden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="js/lib/marked.min.js"></script>
|
||||
<script src="js/lib/codemirror.min.js"></script>
|
||||
<script src="js/lib/cm-mode-xml.min.js"></script>
|
||||
@@ -315,6 +353,7 @@
|
||||
<script src="js/pages/chat-page.js"></script>
|
||||
<script src="js/pages/login-page.js"></script>
|
||||
<script src="js/pages/settings-page.js"></script>
|
||||
<script src="js/pages/profile-page.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -221,6 +221,40 @@ async function apiRemoveAgentSkill(agentId, slug) {
|
||||
return fetch(`/api/agents/${agentId}/skills/${slug}`, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
// ── Profile API ──
|
||||
|
||||
async function apiGetProfile() {
|
||||
const resp = await fetch('/api/profile');
|
||||
if (!resp.ok) return null;
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
async function apiUpdateProfile(data) {
|
||||
const resp = await fetch('/api/profile', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const err = await resp.json();
|
||||
throw new Error(err.error || 'Fehler beim Speichern');
|
||||
}
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
async function apiChangePassword(currentPassword, newPassword) {
|
||||
const resp = await fetch('/api/profile/password', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ currentPassword, newPassword })
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const err = await resp.json();
|
||||
throw new Error(err.error || 'Fehler beim Ändern');
|
||||
}
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
async function apiBackupDb() {
|
||||
const resp = await fetch('/api/backup', { method: 'POST' });
|
||||
if (!resp.ok) throw new Error('Backup fehlgeschlagen');
|
||||
|
||||
@@ -8,11 +8,10 @@ registerPage('dashboard', {
|
||||
navigateTo('settings');
|
||||
});
|
||||
|
||||
// Logout-Link
|
||||
document.getElementById('dashboardLogoutLink').addEventListener('click', async (e) => {
|
||||
// Profil-Link
|
||||
document.getElementById('dashboardProfileLink').addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
await apiLogout();
|
||||
navigateTo('login');
|
||||
navigateTo('profile');
|
||||
});
|
||||
|
||||
// Globaler Click-Handler zum Schließen aller Card-Menüs
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
// ── Profile Page ──
|
||||
|
||||
registerPage('profile', {
|
||||
async init() {
|
||||
var profile = await apiGetProfile();
|
||||
if (!profile) return;
|
||||
|
||||
var usernameInput = document.getElementById('profileUsername');
|
||||
var displayNameInput = document.getElementById('profileDisplayName');
|
||||
var personaInput = document.getElementById('profilePersona');
|
||||
var saveBtn = document.getElementById('profileSaveBtn');
|
||||
var messageDiv = document.getElementById('profileMessage');
|
||||
|
||||
var currentPwInput = document.getElementById('profileCurrentPassword');
|
||||
var newPwInput = document.getElementById('profileNewPassword');
|
||||
var confirmPwInput = document.getElementById('profileConfirmPassword');
|
||||
var passwordBtn = document.getElementById('profilePasswordBtn');
|
||||
var passwordMessageDiv = document.getElementById('profilePasswordMessage');
|
||||
|
||||
var logoutBtn = document.getElementById('profileLogoutBtn');
|
||||
|
||||
// Felder befüllen
|
||||
usernameInput.value = profile.username || '';
|
||||
displayNameInput.value = profile.displayName || '';
|
||||
personaInput.value = profile.persona || '';
|
||||
|
||||
// Profil speichern
|
||||
saveBtn.addEventListener('click', async function() {
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.textContent = 'Speichern...';
|
||||
messageDiv.hidden = true;
|
||||
|
||||
try {
|
||||
var updated = await apiUpdateProfile({
|
||||
displayName: displayNameInput.value.trim(),
|
||||
persona: personaInput.value.trim()
|
||||
});
|
||||
|
||||
// currentUser aktualisieren
|
||||
if (currentUser) {
|
||||
currentUser.displayName = updated.displayName;
|
||||
}
|
||||
|
||||
messageDiv.textContent = 'Profil gespeichert';
|
||||
messageDiv.className = 'profile-message profile-message-success';
|
||||
messageDiv.hidden = false;
|
||||
setTimeout(function() { messageDiv.hidden = true; }, 3000);
|
||||
} catch (err) {
|
||||
messageDiv.textContent = err.message;
|
||||
messageDiv.className = 'profile-message profile-message-error';
|
||||
messageDiv.hidden = false;
|
||||
} finally {
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.textContent = 'Speichern';
|
||||
}
|
||||
});
|
||||
|
||||
// Passwort ändern
|
||||
passwordBtn.addEventListener('click', async function() {
|
||||
passwordMessageDiv.hidden = true;
|
||||
|
||||
var currentPw = currentPwInput.value;
|
||||
var newPw = newPwInput.value;
|
||||
var confirmPw = confirmPwInput.value;
|
||||
|
||||
if (!currentPw || !newPw) {
|
||||
passwordMessageDiv.textContent = 'Alle Felder ausfüllen';
|
||||
passwordMessageDiv.className = 'profile-message profile-message-error';
|
||||
passwordMessageDiv.hidden = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPw !== confirmPw) {
|
||||
passwordMessageDiv.textContent = 'Neue Passwörter stimmen nicht überein';
|
||||
passwordMessageDiv.className = 'profile-message profile-message-error';
|
||||
passwordMessageDiv.hidden = false;
|
||||
return;
|
||||
}
|
||||
|
||||
passwordBtn.disabled = true;
|
||||
passwordBtn.textContent = 'Ändern...';
|
||||
|
||||
try {
|
||||
await apiChangePassword(currentPw, newPw);
|
||||
|
||||
currentPwInput.value = '';
|
||||
newPwInput.value = '';
|
||||
confirmPwInput.value = '';
|
||||
|
||||
passwordMessageDiv.textContent = 'Passwort geändert';
|
||||
passwordMessageDiv.className = 'profile-message profile-message-success';
|
||||
passwordMessageDiv.hidden = false;
|
||||
setTimeout(function() { passwordMessageDiv.hidden = true; }, 3000);
|
||||
} catch (err) {
|
||||
passwordMessageDiv.textContent = err.message;
|
||||
passwordMessageDiv.className = 'profile-message profile-message-error';
|
||||
passwordMessageDiv.hidden = false;
|
||||
} finally {
|
||||
passwordBtn.disabled = false;
|
||||
passwordBtn.textContent = 'Passwort ändern';
|
||||
}
|
||||
});
|
||||
|
||||
// Logout
|
||||
logoutBtn.addEventListener('click', async function() {
|
||||
await apiLogout();
|
||||
currentUser = null;
|
||||
navigateTo('login');
|
||||
});
|
||||
},
|
||||
destroy() {}
|
||||
});
|
||||
Reference in new issue
Block a user