149 lines
6.3 KiB
HTML
149 lines
6.3 KiB
HTML
<!doctype html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>CRM – Mein Profil</title>
|
||
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<link rel="stylesheet" href="/css/app.css" />
|
||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.1/dist/cdn.min.js"></script>
|
||
|
||
<script type="module" src="/js/store.js"></script>
|
||
<script type="module" src="/js/components/notifications.js"></script>
|
||
</head>
|
||
<body class="min-h-screen bg-slate-50">
|
||
|
||
<div x-data="toastContainer()" class="crm-toast-container" x-cloak>
|
||
<template x-for="n in items" :key="n.id">
|
||
<div class="crm-toast" :class="'toast-' + n.type"
|
||
@click="dismiss(n.id)" x-text="n.message"></div>
|
||
</template>
|
||
</div>
|
||
|
||
<div x-data="{ init: () => Alpine.store('auth').init() }" x-init="init()" class="flex min-h-screen">
|
||
|
||
<aside class="crm-sidebar">
|
||
<div class="px-6 py-4 text-white text-lg font-bold border-b border-slate-700">
|
||
<a href="/app.html" class="!text-white !border-0">CRM</a>
|
||
</div>
|
||
<nav class="mt-2">
|
||
<a href="/app.html">Dashboard</a>
|
||
<a href="/accounts.html">Accounts</a>
|
||
<a href="/contacts.html">Contacts</a>
|
||
<a href="/pipeline.html">Pipeline</a>
|
||
<a href="/activities.html">Activities</a>
|
||
<a href="/settings-profile.html" class="active">Profil</a>
|
||
<a href="/settings-users.html" x-show="$store.auth.isAdmin">Users (Admin)</a>
|
||
<a href="/settings-org.html" x-show="$store.auth.isAdmin">Org (Admin)</a>
|
||
</nav>
|
||
</aside>
|
||
|
||
<div class="flex-1 flex flex-col">
|
||
<header class="bg-white border-b border-slate-200 px-6 py-3 flex items-center justify-between">
|
||
<h1 class="text-xl font-semibold text-slate-800">Mein Profil</h1>
|
||
<div class="flex items-center gap-3 text-sm">
|
||
<span class="text-slate-600">
|
||
<strong class="text-slate-900"
|
||
x-text="$store.auth.user ? $store.auth.user.name : '…'"></strong>
|
||
</span>
|
||
<button class="btn-secondary" @click="$store.auth.logout()">Logout</button>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="flex-1 p-6 max-w-2xl" x-data="profileForm()" x-init="init()">
|
||
|
||
<div x-show="loading" class="crm-card text-center py-8">
|
||
<div class="crm-spinner mx-auto"></div>
|
||
</div>
|
||
|
||
<div x-show="!loading && user" class="crm-card">
|
||
<h2 class="text-lg font-semibold mb-4">Profil bearbeiten</h2>
|
||
|
||
<!-- Read-only fields -->
|
||
<div class="grid grid-cols-2 gap-3 mb-4 text-sm">
|
||
<div>
|
||
<label class="crm-label">E-Mail (nicht änderbar)</label>
|
||
<input type="email" disabled :value="user?.email || ''" class="crm-input" />
|
||
</div>
|
||
<div>
|
||
<label class="crm-label">Rolle (nicht änderbar)</label>
|
||
<input type="text" disabled :value="user?.role || ''" class="crm-input" />
|
||
</div>
|
||
</div>
|
||
|
||
<form @submit.prevent="submit()">
|
||
<div class="mb-3">
|
||
<label class="crm-label">Name *</label>
|
||
<input type="text" required minlength="1" maxlength="255"
|
||
x-model="form.name" class="crm-input" />
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="crm-label">Avatar-URL</label>
|
||
<input type="text" maxlength="1024"
|
||
x-model="form.avatar_url" class="crm-input" />
|
||
</div>
|
||
<div class="mb-3 flex items-center gap-2">
|
||
<input type="checkbox" id="notif" x-model="form.email_notifications" />
|
||
<label for="notif" class="text-sm">E-Mail-Benachrichtigungen erhalten</label>
|
||
</div>
|
||
|
||
<div x-show="error" class="mb-3 p-2 bg-red-50 text-red-700 text-sm rounded"
|
||
x-text="error"></div>
|
||
|
||
<div class="flex gap-2 justify-end mt-4">
|
||
<button type="submit" :disabled="saving" class="btn-primary"
|
||
x-text="saving ? 'Speichere …' : 'Speichern'"></button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
function profileForm() {
|
||
return {
|
||
user: null,
|
||
loading: true,
|
||
saving: false,
|
||
error: '',
|
||
form: { name: '', avatar_url: '', email_notifications: true },
|
||
|
||
async init() {
|
||
// wait for auth store to load user
|
||
const auth = Alpine.store('auth');
|
||
if (!auth.user) {
|
||
try { await auth.fetchUser(); } catch { /* 401 → redirect */ }
|
||
}
|
||
this.user = auth.user;
|
||
if (this.user) {
|
||
this.form.name = this.user.name || '';
|
||
this.form.avatar_url = this.user.avatar_url || '';
|
||
this.form.email_notifications = this.user.email_notifications !== false;
|
||
}
|
||
this.loading = false;
|
||
},
|
||
|
||
async submit() {
|
||
this.saving = true; this.error = '';
|
||
try {
|
||
const { api } = await import('/js/api.js');
|
||
const payload = {
|
||
name: this.form.name,
|
||
email_notifications: this.form.email_notifications,
|
||
};
|
||
if (this.form.avatar_url) payload.avatar_url = this.form.avatar_url;
|
||
const updated = await api.patch(`/users/${this.user.id}`, payload);
|
||
Alpine.store('auth').user = updated;
|
||
Alpine.store('notifications').success('Profil aktualisiert.');
|
||
} catch (err) {
|
||
this.error = err.message || 'Fehler beim Speichern.';
|
||
} finally { this.saving = false; }
|
||
},
|
||
};
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|