feat(theme): implement single-file CSS theming engine with dark, light, high-contrast, and soft themes
MindSift CI & Docker Release / Build, Test in Container & Push (push) Failing after 16s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Failing after 16s
This commit is contained in:
1 parent
0f24cbdbf4
commit
b3e651b3b9
18 files changed
+1455
-804
No files matched your search
@@ -382,40 +382,38 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
<span>Zurück</span>
|
||||
</button>
|
||||
<div>
|
||||
<h1 className="text-base font-bold text-white tracking-tight flex items-center gap-2">
|
||||
<h1 className="text-base font-bold text-main tracking-tight flex items-center gap-2">
|
||||
<Shield className="w-4 h-4 text-accent" />
|
||||
<span>Administration & Team-Steuerung</span>
|
||||
</h1>
|
||||
<p className="text-xs text-zinc-400">
|
||||
<p className="text-xs text-muted">
|
||||
Benutzerverwaltung, Einladungscodes, Organisationen, Teams, Isolationsmodus & Mail-Server
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className="px-2.5 py-1 bg-accent/15 border border-accent text-accent text-xs font-semibold">
|
||||
<span className="badge-accent">
|
||||
{isServerAdmin ? 'Server-Administrator' : 'Team-Administrator'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{statusMsg && (
|
||||
<div
|
||||
className={`p-3 border text-xs flex items-center justify-between gap-2 ${
|
||||
statusMsg.type === 'ok'
|
||||
? 'bg-blue-950/30 border-accent text-blue-200'
|
||||
: 'bg-red-950/30 border-red-500 text-red-200'
|
||||
className={`p-3 text-xs flex items-center justify-between gap-2 rounded-card ${
|
||||
statusMsg.type === 'ok' ? 'alert-ok' : 'alert-err'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{statusMsg.type === 'ok' ? (
|
||||
<Check className="w-4 h-4 text-accent flex-shrink-0" />
|
||||
) : (
|
||||
<AlertCircle className="w-4 h-4 text-red-400 flex-shrink-0" />
|
||||
<AlertCircle className="w-4 h-4 text-status-err flex-shrink-0" />
|
||||
)}
|
||||
<span>{statusMsg.text}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setStatusMsg(null)}
|
||||
className="text-zinc-400 hover:text-white text-[11px]"
|
||||
className="text-muted hover:text-main text-[11px]"
|
||||
>
|
||||
Schließen
|
||||
</button>
|
||||
@@ -425,19 +423,19 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
{/* Main 2-Column Layout */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 items-start">
|
||||
{/* Left Navigation Sidebar */}
|
||||
<aside className="bg-panel border border-border divide-y divide-border">
|
||||
<aside className="panel-surface divide-y divide-border overflow-hidden">
|
||||
<button
|
||||
onClick={() => setSection('users')}
|
||||
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||
section === 'users'
|
||||
? 'bg-panel-elevated text-white border-l-2 border-l-accent'
|
||||
: 'text-zinc-400 hover:text-white hover:bg-white/[0.02]'
|
||||
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||
: 'text-muted hover:text-main hover:bg-canvas/50'
|
||||
}`}
|
||||
>
|
||||
<Users className="w-4 h-4 text-accent flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-semibold">Benutzer & Einladungen</p>
|
||||
<p className="text-[10px] text-zinc-500">Konten, Rollen & Invite-Codes</p>
|
||||
<p className="text-[10px] text-faint">Konten, Rollen & Invite-Codes</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -445,14 +443,14 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
onClick={() => setSection('orgs')}
|
||||
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||
section === 'orgs'
|
||||
? 'bg-panel-elevated text-white border-l-2 border-l-accent'
|
||||
: 'text-zinc-400 hover:text-white hover:bg-white/[0.02]'
|
||||
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||
: 'text-muted hover:text-main hover:bg-canvas/50'
|
||||
}`}
|
||||
>
|
||||
<Building2 className="w-4 h-4 text-accent flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-semibold">Organisationen & Teams</p>
|
||||
<p className="text-[10px] text-zinc-500">Teams & Mitglieder zuweisen</p>
|
||||
<p className="text-[10px] text-faint">Teams & Mitglieder zuweisen</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -462,14 +460,14 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
onClick={() => setSection('server')}
|
||||
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||
section === 'server'
|
||||
? 'bg-panel-elevated text-white border-l-2 border-l-accent'
|
||||
: 'text-zinc-400 hover:text-white hover:bg-white/[0.02]'
|
||||
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||
: 'text-muted hover:text-main hover:bg-canvas/50'
|
||||
}`}
|
||||
>
|
||||
<Server className="w-4 h-4 text-accent flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-semibold">Server & Isolation</p>
|
||||
<p className="text-[10px] text-zinc-500">Sichtbarkeit & Registrierung</p>
|
||||
<p className="text-[10px] text-faint">Sichtbarkeit & Registrierung</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -477,14 +475,14 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
onClick={() => setSection('smtp')}
|
||||
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||
section === 'smtp'
|
||||
? 'bg-panel-elevated text-white border-l-2 border-l-accent'
|
||||
: 'text-zinc-400 hover:text-white hover:bg-white/[0.02]'
|
||||
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||
: 'text-muted hover:text-main hover:bg-canvas/50'
|
||||
}`}
|
||||
>
|
||||
<Mail className="w-4 h-4 text-accent flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-semibold">E-Mail / SMTP-Server</p>
|
||||
<p className="text-[10px] text-zinc-500">Mailversand für Alarme</p>
|
||||
<p className="text-[10px] text-faint">Mailversand für Alarme</p>
|
||||
</div>
|
||||
</button>
|
||||
</>
|
||||
@@ -492,23 +490,23 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
</aside>
|
||||
|
||||
{/* Right Content Area */}
|
||||
<div className="md:col-span-3 bg-panel border border-border p-5 text-xs">
|
||||
<div className="md:col-span-3 panel-surface p-5 text-xs">
|
||||
{/* ================================================================= */}
|
||||
{/* SECTION 1: BENUTZER, ROLLEN & EINLADUNGEN */}
|
||||
{/* ================================================================= */}
|
||||
{section === 'users' && (
|
||||
<div className="space-y-6">
|
||||
<div className="border-b border-border pb-3">
|
||||
<h2 className="text-sm font-bold text-white">Benutzersteuerung, Rollen & Einladungen</h2>
|
||||
<p className="text-zinc-400 mt-0.5">
|
||||
<h2 className="text-sm font-bold text-main">Benutzersteuerung, Rollen & Einladungen</h2>
|
||||
<p className="text-muted mt-0.5">
|
||||
Lege Benutzerkonten direkt an oder erzeuge Einladungscodes, mit denen sich eingeladene
|
||||
Personen selbst registrieren können, auch wenn die öffentliche Registrierung gesperrt ist.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Direct User Creation Form (Fixed Layout: 4 clean columns with min-w-0, no overflow!) */}
|
||||
<div className="p-4 bg-canvas border border-border space-y-3">
|
||||
<h3 className="font-bold text-white flex items-center gap-1.5">
|
||||
{/* Direct User Creation Form */}
|
||||
<div className="p-4 bg-canvas border border-border rounded-card space-y-3">
|
||||
<h3 className="font-bold text-main flex items-center gap-1.5">
|
||||
<UserPlus className="w-3.5 h-3.5 text-accent" />
|
||||
<span>Neues Benutzerkonto direkt anlegen</span>
|
||||
</h3>
|
||||
@@ -516,31 +514,31 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
<form onSubmit={handleCreateUser} className="space-y-3">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
<div className="min-w-0">
|
||||
<label className="block text-zinc-400 mb-1">Anzeigename</label>
|
||||
<label className="block text-muted mb-1">Anzeigename</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newUserName}
|
||||
onChange={(e) => setNewUserName(e.target.value)}
|
||||
placeholder="z.B. Anna Beispiel"
|
||||
required
|
||||
className="w-full min-w-0 h-8 px-2.5 input-field text-white"
|
||||
className="w-full min-w-0 h-8 px-2.5 input-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="min-w-0">
|
||||
<label className="block text-zinc-400 mb-1">E-Mail-Adresse</label>
|
||||
<label className="block text-muted mb-1">E-Mail-Adresse</label>
|
||||
<input
|
||||
type="email"
|
||||
value={newUserEmail}
|
||||
onChange={(e) => setNewUserEmail(e.target.value)}
|
||||
placeholder="anna@beispiel.de"
|
||||
required
|
||||
className="w-full min-w-0 h-8 px-2.5 input-field text-white"
|
||||
className="w-full min-w-0 h-8 px-2.5 input-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="min-w-0">
|
||||
<label className="block text-zinc-400 mb-1">Initial-Passwort</label>
|
||||
<label className="block text-muted mb-1">Initial-Passwort</label>
|
||||
<input
|
||||
type="password"
|
||||
value={newUserPassword}
|
||||
@@ -548,16 +546,16 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
placeholder="Mind. 4 Zeichen"
|
||||
minLength={4}
|
||||
required
|
||||
className="w-full min-w-0 h-8 px-2.5 input-field text-white"
|
||||
className="w-full min-w-0 h-8 px-2.5 input-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="min-w-0">
|
||||
<label className="block text-zinc-400 mb-1">Benutzerrolle</label>
|
||||
<label className="block text-muted mb-1">Benutzerrolle</label>
|
||||
<select
|
||||
value={newUserRole}
|
||||
onChange={(e) => setNewUserRole(e.target.value as UserRole)}
|
||||
className="w-full min-w-0 h-8 px-2 input-field text-white bg-panel"
|
||||
className="w-full min-w-0 h-8 px-2 input-field bg-panel"
|
||||
>
|
||||
<option value="member">Mitglied (Standard)</option>
|
||||
<option value="team_admin">Team-Admin (Darf einladen)</option>
|
||||
@@ -579,8 +577,8 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Invite Code Generator */}
|
||||
<div className="p-4 bg-canvas border border-border space-y-3">
|
||||
<h3 className="font-bold text-white flex items-center gap-1.5">
|
||||
<div className="p-4 bg-canvas border border-border rounded-card space-y-3">
|
||||
<h3 className="font-bold text-main flex items-center gap-1.5">
|
||||
<KeyRound className="w-3.5 h-3.5 text-accent" />
|
||||
<span>Einladungscode erzeugen (gültig für 7 Tage)</span>
|
||||
</h3>
|
||||
@@ -588,7 +586,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
<form onSubmit={handleCreateInvite} className="space-y-3">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
<div className="min-w-0">
|
||||
<label className="block text-zinc-400 mb-1">
|
||||
<label className="block text-muted mb-1">
|
||||
Gebundene E-Mail (optional)
|
||||
</label>
|
||||
<input
|
||||
@@ -596,16 +594,16 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
value={inviteEmail}
|
||||
onChange={(e) => setInviteEmail(e.target.value)}
|
||||
placeholder="Leer lassen für beliebige E-Mail"
|
||||
className="w-full min-w-0 h-8 px-2.5 input-field text-white"
|
||||
className="w-full min-w-0 h-8 px-2.5 input-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="min-w-0">
|
||||
<label className="block text-zinc-400 mb-1">Zugewiesene Rolle</label>
|
||||
<label className="block text-muted mb-1">Zugewiesene Rolle</label>
|
||||
<select
|
||||
value={inviteRole}
|
||||
onChange={(e) => setInviteRole(e.target.value as UserRole)}
|
||||
className="w-full min-w-0 h-8 px-2 input-field text-white bg-panel"
|
||||
className="w-full min-w-0 h-8 px-2 input-field bg-panel"
|
||||
>
|
||||
<option value="member">Mitglied</option>
|
||||
<option value="team_admin">Team-Admin</option>
|
||||
@@ -614,13 +612,13 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="min-w-0">
|
||||
<label className="block text-zinc-400 mb-1">
|
||||
<label className="block text-muted mb-1">
|
||||
Automatisch Team zuweisen (optional)
|
||||
</label>
|
||||
<select
|
||||
value={inviteTeamId}
|
||||
onChange={(e) => setInviteTeamId(e.target.value)}
|
||||
className="w-full min-w-0 h-8 px-2 input-field text-white bg-panel"
|
||||
className="w-full min-w-0 h-8 px-2 input-field bg-panel"
|
||||
>
|
||||
<option value="">Kein direktes Team</option>
|
||||
{flattenedTeams.map((t) => (
|
||||
@@ -635,7 +633,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn-action h-8 px-4 text-xs font-semibold flex items-center gap-1.5 border-accent text-white"
|
||||
className="btn-action h-8 px-4 text-xs font-semibold flex items-center gap-1.5 border-accent text-main"
|
||||
>
|
||||
<KeyRound className="w-3.5 h-3.5 text-accent" />
|
||||
<span>Einladungscode generieren</span>
|
||||
@@ -646,29 +644,29 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
{/* Active Invites List */}
|
||||
{invites.length > 0 && (
|
||||
<div className="pt-2 border-t border-border space-y-1.5">
|
||||
<p className="text-[11px] font-semibold text-zinc-400">
|
||||
<p className="text-[11px] font-semibold text-muted">
|
||||
Aktive & bisherige Einladungscodes:
|
||||
</p>
|
||||
<div className="divide-y divide-border border border-border bg-panel">
|
||||
<div className="divide-y divide-border border border-border bg-panel rounded-card overflow-hidden">
|
||||
{invites.map((inv) => (
|
||||
<div
|
||||
key={inv.id}
|
||||
className="p-2 flex flex-wrap items-center justify-between gap-2"
|
||||
>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<code className="px-2 py-0.5 bg-canvas border border-border font-mono font-bold text-accent">
|
||||
<code className="px-2 py-0.5 bg-canvas border border-border font-mono font-bold text-accent rounded-card">
|
||||
{inv.code}
|
||||
</code>
|
||||
<span className="text-zinc-300">
|
||||
Rolle: <strong>{inv.role}</strong>
|
||||
<span className="text-muted">
|
||||
Rolle: <strong className="text-main">{inv.role}</strong>
|
||||
{inv.email ? ` · Für: ${inv.email}` : ''}
|
||||
</span>
|
||||
{inv.usedBy ? (
|
||||
<span className="px-1.5 py-0.5 bg-zinc-800 text-zinc-400 text-[10px]">
|
||||
<span className="badge-neutral text-[10px]">
|
||||
Eingelöst
|
||||
</span>
|
||||
) : (
|
||||
<span className="px-1.5 py-0.5 bg-emerald-950/60 border border-emerald-500/40 text-emerald-300 text-[10px]">
|
||||
<span className="badge-ok text-[10px]">
|
||||
Offen
|
||||
</span>
|
||||
)}
|
||||
@@ -701,23 +699,23 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
|
||||
{/* Users List */}
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-bold text-white">
|
||||
<h3 className="font-bold text-main">
|
||||
Registrierte Benutzer ({users.length})
|
||||
</h3>
|
||||
<div className="border border-border divide-y divide-border bg-canvas">
|
||||
<div className="border border-border divide-y divide-border bg-canvas rounded-card overflow-hidden">
|
||||
{users.map((u) => (
|
||||
<div key={u.id} className="p-3 space-y-2">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-bold text-white">{u.displayName}</span>
|
||||
<span className="font-bold text-main">{u.displayName}</span>
|
||||
{!u.isActive && (
|
||||
<span className="px-1.5 py-0.5 bg-red-950 text-red-300 border border-red-500/40 text-[10px]">
|
||||
<span className="badge-err text-[10px]">
|
||||
Gesperrt
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-zinc-400 text-[11px]">{u.email}</p>
|
||||
<p className="text-muted text-[11px]">{u.email}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
@@ -726,7 +724,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
value={u.role}
|
||||
disabled={!isServerAdmin && u.role === 'admin'}
|
||||
onChange={(e) => handleChangeUserRole(u, e.target.value as UserRole)}
|
||||
className="h-7 px-2 input-field text-[11px] text-white bg-panel"
|
||||
className="h-7 px-2 input-field text-[11px] bg-panel"
|
||||
title="Benutzerrolle ändern"
|
||||
>
|
||||
<option value="member">Mitglied</option>
|
||||
@@ -775,7 +773,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
value={resetPwdVal}
|
||||
onChange={(e) => setResetPwdVal(e.target.value)}
|
||||
placeholder="Neues Passwort eingeben…"
|
||||
className="flex-1 h-7 px-2 input-field text-white"
|
||||
className="flex-1 h-7 px-2 input-field"
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleConfirmResetPassword(u.id)}
|
||||
@@ -804,8 +802,8 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
{section === 'orgs' && (
|
||||
<div className="space-y-6">
|
||||
<div className="border-b border-border pb-3">
|
||||
<h2 className="text-sm font-bold text-white">Organisationen & Teams verwalten</h2>
|
||||
<p className="text-zinc-400 mt-0.5">
|
||||
<h2 className="text-sm font-bold text-main">Organisationen & Teams verwalten</h2>
|
||||
<p className="text-muted mt-0.5">
|
||||
Erstelle Organisationen und unterteile sie in Teams. Weise Benutzern Teams zu, damit
|
||||
sie im Modus "Teams isoliert" gemeinsam an Listen und Aufgaben arbeiten können.
|
||||
</p>
|
||||
@@ -813,8 +811,8 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
|
||||
{/* Create Organization & Team */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<div className="p-4 bg-canvas border border-border space-y-3">
|
||||
<h3 className="font-bold text-white">1. Neue Organisation gründen</h3>
|
||||
<div className="p-4 bg-canvas border border-border rounded-card space-y-3">
|
||||
<h3 className="font-bold text-main">1. Neue Organisation gründen</h3>
|
||||
<form onSubmit={handleCreateOrg} className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
@@ -822,7 +820,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
onChange={(e) => setNewOrgName(e.target.value)}
|
||||
placeholder="z.B. Homelab, Familie, Firma…"
|
||||
required
|
||||
className="flex-1 min-w-0 h-8 px-2.5 input-field text-white"
|
||||
className="flex-1 min-w-0 h-8 px-2.5 input-field"
|
||||
/>
|
||||
<button type="submit" className="btn-primary h-8 px-3 text-xs flex-shrink-0">
|
||||
Anlegen
|
||||
@@ -830,10 +828,10 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-canvas border border-border space-y-3">
|
||||
<h3 className="font-bold text-white">2. Neues Team in Organisation anlegen</h3>
|
||||
<div className="p-4 bg-canvas border border-border rounded-card space-y-3">
|
||||
<h3 className="font-bold text-main">2. Neues Team in Organisation anlegen</h3>
|
||||
{orgs.length === 0 ? (
|
||||
<p className="text-zinc-500">
|
||||
<p className="text-faint">
|
||||
Bitte lege zuerst links eine Organisation an.
|
||||
</p>
|
||||
) : (
|
||||
@@ -842,7 +840,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
<select
|
||||
value={selectedOrgId}
|
||||
onChange={(e) => setSelectedOrgId(e.target.value)}
|
||||
className="w-full min-w-0 h-8 px-2 input-field text-white bg-panel"
|
||||
className="w-full min-w-0 h-8 px-2 input-field bg-panel"
|
||||
>
|
||||
{orgs.map((o) => (
|
||||
<option key={o.id} value={o.id}>
|
||||
@@ -856,7 +854,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
onChange={(e) => setNewTeamName(e.target.value)}
|
||||
placeholder="Team-Name (z.B. Infrastruktur)"
|
||||
required
|
||||
className="w-full min-w-0 h-8 px-2.5 input-field text-white"
|
||||
className="w-full min-w-0 h-8 px-2.5 input-field"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
@@ -865,7 +863,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
value={newTeamDesc}
|
||||
onChange={(e) => setNewTeamDesc(e.target.value)}
|
||||
placeholder="Kurzbeschreibung (optional)…"
|
||||
className="flex-1 min-w-0 h-8 px-2.5 input-field text-white"
|
||||
className="flex-1 min-w-0 h-8 px-2.5 input-field"
|
||||
/>
|
||||
<button type="submit" className="btn-primary h-8 px-3 text-xs flex-shrink-0">
|
||||
Team erstellen
|
||||
@@ -879,19 +877,19 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
{/* Organizations & Nested Teams Display */}
|
||||
<div className="space-y-4">
|
||||
{orgs.length === 0 ? (
|
||||
<div className="p-6 bg-canvas border border-border text-center text-zinc-500">
|
||||
<div className="p-6 bg-canvas border border-border rounded-card text-center text-faint">
|
||||
Noch keine Organisationen vorhanden.
|
||||
</div>
|
||||
) : (
|
||||
orgs.map((org) => {
|
||||
const orgTeams = Array.isArray(org.teams) ? org.teams : [];
|
||||
return (
|
||||
<div key={org.id} className="bg-canvas border border-border">
|
||||
<div key={org.id} className="bg-canvas border border-border rounded-card overflow-hidden">
|
||||
<div className="p-3 bg-panel-elevated border-b border-border flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Building2 className="w-4 h-4 text-accent" />
|
||||
<span className="font-bold text-sm text-white">{org.name}</span>
|
||||
<span className="text-[11px] text-zinc-400">
|
||||
<span className="font-bold text-sm text-main">{org.name}</span>
|
||||
<span className="text-[11px] text-muted">
|
||||
({orgTeams.length} {orgTeams.length === 1 ? 'Team' : 'Teams'})
|
||||
</span>
|
||||
</div>
|
||||
@@ -906,7 +904,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
|
||||
<div className="p-3 space-y-3">
|
||||
{orgTeams.length === 0 ? (
|
||||
<p className="text-zinc-500 text-xs">
|
||||
<p className="text-faint text-xs">
|
||||
Noch keine Teams in dieser Organisation.
|
||||
</p>
|
||||
) : (
|
||||
@@ -915,16 +913,16 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
return (
|
||||
<div
|
||||
key={team.id}
|
||||
className="p-3 bg-panel border border-border space-y-3"
|
||||
className="p-3 bg-panel border border-border rounded-card space-y-3"
|
||||
>
|
||||
<div className="flex items-center justify-between border-b border-border pb-2">
|
||||
<div>
|
||||
<span className="font-bold text-white flex items-center gap-1.5">
|
||||
<span className="font-bold text-main flex items-center gap-1.5">
|
||||
<Users className="w-3.5 h-3.5 text-accent" />
|
||||
<span>{team.name}</span>
|
||||
</span>
|
||||
{team.description && (
|
||||
<p className="text-[11px] text-zinc-400 mt-0.5">
|
||||
<p className="text-[11px] text-muted mt-0.5">
|
||||
{team.description}
|
||||
</p>
|
||||
)}
|
||||
@@ -940,11 +938,11 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
|
||||
{/* Team Members */}
|
||||
<div className="space-y-1.5">
|
||||
<p className="text-[11px] font-semibold text-zinc-400">
|
||||
<p className="text-[11px] font-semibold text-muted">
|
||||
Mitglieder ({members.length}):
|
||||
</p>
|
||||
{members.length === 0 ? (
|
||||
<p className="text-[11px] text-zinc-500">
|
||||
<p className="text-[11px] text-faint">
|
||||
Noch keine Mitglieder zugewiesen.
|
||||
</p>
|
||||
) : (
|
||||
@@ -952,19 +950,19 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
{members.map((m) => (
|
||||
<div
|
||||
key={m.userId}
|
||||
className="px-2 py-1 bg-canvas border border-border flex items-center gap-2"
|
||||
className="px-2 py-1 bg-canvas border border-border rounded-btn flex items-center gap-2"
|
||||
>
|
||||
<span className="text-white font-medium">
|
||||
<span className="text-main font-medium">
|
||||
{m.displayName}
|
||||
</span>
|
||||
<span className="text-[10px] text-zinc-400">
|
||||
<span className="text-[10px] text-muted">
|
||||
({m.role})
|
||||
</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
handleRemoveMemberFromTeam(team.id, m.userId)
|
||||
}
|
||||
className="text-zinc-500 hover:text-red-400"
|
||||
className="text-faint hover:text-status-err"
|
||||
title="Aus Team entfernen"
|
||||
>
|
||||
×
|
||||
@@ -985,7 +983,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
[team.id]: e.target.value,
|
||||
}))
|
||||
}
|
||||
className="flex-1 min-w-[160px] h-7 px-2 input-field text-[11px] text-white bg-canvas"
|
||||
className="flex-1 min-w-[160px] h-7 px-2 input-field text-[11px] bg-canvas"
|
||||
>
|
||||
<option value="">Benutzer auswählen…</option>
|
||||
{users.map((u) => (
|
||||
@@ -1003,7 +1001,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
[team.id]: e.target.value,
|
||||
}))
|
||||
}
|
||||
className="h-7 px-2 input-field text-[11px] text-white bg-canvas"
|
||||
className="h-7 px-2 input-field text-[11px] bg-canvas"
|
||||
>
|
||||
<option value="member">Mitglied</option>
|
||||
<option value="team_admin">Team-Admin</option>
|
||||
@@ -1037,10 +1035,10 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
{section === 'server' && isServerAdmin && (
|
||||
<div className="space-y-6">
|
||||
<div className="border-b border-border pb-3">
|
||||
<h2 className="text-sm font-bold text-white">
|
||||
<h2 className="text-sm font-bold text-main">
|
||||
Server-Grundeinstellungen & Benutzer-Sichtbarkeit
|
||||
</h2>
|
||||
<p className="text-zinc-400 mt-0.5">
|
||||
<p className="text-muted mt-0.5">
|
||||
Als Server-Admin bestimmst du zentral für alle Nutzer, ob sie komplett isoliert
|
||||
arbeiten, sich nur innerhalb ihrer Teams sehen oder ob alle Nutzer für alle sichtbar sind.
|
||||
</p>
|
||||
@@ -1048,7 +1046,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-zinc-300 font-semibold mb-1">
|
||||
<label className="block text-muted font-semibold mb-1">
|
||||
Name der MindSift-Instanz
|
||||
</label>
|
||||
<div className="flex gap-2 max-w-md">
|
||||
@@ -1058,7 +1056,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
onChange={(e) =>
|
||||
setSettings((prev) => ({ ...prev, instanceName: e.target.value }))
|
||||
}
|
||||
className="flex-1 h-9 px-3 input-field text-white"
|
||||
className="flex-1 h-9 px-3 input-field"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -1072,7 +1070,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
|
||||
{/* Sharing Isolation Cards */}
|
||||
<div className="space-y-2">
|
||||
<label className="block text-zinc-300 font-semibold flex items-center gap-1.5">
|
||||
<label className="block text-muted font-semibold flex items-center gap-1.5">
|
||||
<Share2 className="w-4 h-4 text-accent" />
|
||||
<span>Globale Sharing- & Sichtbarkeits-Isolation</span>
|
||||
</label>
|
||||
@@ -1081,14 +1079,14 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSaveSystemSettings({ sharingMode: 'strict_private' })}
|
||||
className={`p-4 text-left border transition-colors ${
|
||||
className={`p-4 text-left border rounded-card transition-colors ${
|
||||
settings.sharingMode === 'strict_private'
|
||||
? 'bg-canvas border-accent text-white'
|
||||
: 'bg-canvas/50 border-border text-zinc-400 hover:text-white'
|
||||
? 'bg-canvas border-accent text-main shadow-panel'
|
||||
: 'bg-canvas/50 border-border text-muted hover:text-main'
|
||||
}`}
|
||||
>
|
||||
<p className="font-bold text-xs text-white">1. Strikt Privat (Keiner sieht andere)</p>
|
||||
<p className="text-[11px] text-zinc-400 mt-1 leading-relaxed">
|
||||
<p className="font-bold text-xs text-main">1. Strikt Privat (Keiner sieht andere)</p>
|
||||
<p className="text-[11px] text-muted mt-1 leading-relaxed">
|
||||
Jeder Benutzer arbeitet komplett für sich allein. Keine geteilten Spaces und keine
|
||||
Benutzersuche möglich.
|
||||
</p>
|
||||
@@ -1097,14 +1095,14 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSaveSystemSettings({ sharingMode: 'teams_only' })}
|
||||
className={`p-4 text-left border transition-colors ${
|
||||
className={`p-4 text-left border rounded-card transition-colors ${
|
||||
settings.sharingMode === 'teams_only'
|
||||
? 'bg-canvas border-accent text-white'
|
||||
: 'bg-canvas/50 border-border text-zinc-400 hover:text-white'
|
||||
? 'bg-canvas border-accent text-main shadow-panel'
|
||||
: 'bg-canvas/50 border-border text-muted hover:text-main'
|
||||
}`}
|
||||
>
|
||||
<p className="font-bold text-xs text-white">2. Teams isoliert (Empfohlen)</p>
|
||||
<p className="text-[11px] text-zinc-400 mt-1 leading-relaxed">
|
||||
<p className="font-bold text-xs text-main">2. Teams isoliert (Empfohlen)</p>
|
||||
<p className="text-[11px] text-muted mt-1 leading-relaxed">
|
||||
Benutzer sehen ausschließlich Personen, mit denen sie mindestens ein gemeinsames Team
|
||||
teilen.
|
||||
</p>
|
||||
@@ -1113,14 +1111,14 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSaveSystemSettings({ sharingMode: 'open' })}
|
||||
className={`p-4 text-left border transition-colors ${
|
||||
className={`p-4 text-left border rounded-card transition-colors ${
|
||||
settings.sharingMode === 'open'
|
||||
? 'bg-canvas border-accent text-white'
|
||||
: 'bg-canvas/50 border-border text-zinc-400 hover:text-white'
|
||||
? 'bg-canvas border-accent text-main shadow-panel'
|
||||
: 'bg-canvas/50 border-border text-muted hover:text-main'
|
||||
}`}
|
||||
>
|
||||
<p className="font-bold text-xs text-white">3. Offen (Alle sehen alle)</p>
|
||||
<p className="text-[11px] text-zinc-400 mt-1 leading-relaxed">
|
||||
<p className="font-bold text-xs text-main">3. Offen (Alle sehen alle)</p>
|
||||
<p className="text-[11px] text-muted mt-1 leading-relaxed">
|
||||
Alle registrierten Benutzer dieser Instanz können sich gegenseitig sehen und Spaces
|
||||
miteinander teilen.
|
||||
</p>
|
||||
@@ -1129,10 +1127,10 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Public Registration Toggle */}
|
||||
<div className="p-4 bg-canvas border border-border flex items-center justify-between gap-4">
|
||||
<div className="p-4 bg-canvas border border-border rounded-card flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<p className="font-bold text-white">Öffentliche Selbstregistrierung erlauben</p>
|
||||
<p className="text-[11px] text-zinc-400 mt-0.5">
|
||||
<p className="font-bold text-main">Öffentliche Selbstregistrierung erlauben</p>
|
||||
<p className="text-[11px] text-muted mt-0.5">
|
||||
Wenn deaktiviert, können sich Fremde auf dem Login-Bildschirm nicht registrieren.
|
||||
Neue Benutzer benötigen zwingend einen Einladungscode oder müssen von einem Admin
|
||||
angelegt werden.
|
||||
@@ -1144,10 +1142,10 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
onClick={() =>
|
||||
handleSaveSystemSettings({ allowRegistration: !settings.allowRegistration })
|
||||
}
|
||||
className={`h-8 px-4 text-xs font-semibold border flex-shrink-0 ${
|
||||
className={`h-8 px-4 text-xs font-semibold border rounded-btn flex-shrink-0 transition-colors ${
|
||||
settings.allowRegistration
|
||||
? 'bg-accent/20 border-accent text-accent'
|
||||
: 'bg-panel border-border text-zinc-400'
|
||||
: 'bg-panel border-border text-muted hover:text-main'
|
||||
}`}
|
||||
>
|
||||
{settings.allowRegistration ? 'Erlaubt (Offen)' : 'Gesperrt (Nur mit Einladung)'}
|
||||
@@ -1164,17 +1162,17 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
<div className="space-y-6">
|
||||
<div className="border-b border-border pb-3 flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
||||
<div>
|
||||
<h2 className="text-sm font-bold text-white">
|
||||
<h2 className="text-sm font-bold text-main">
|
||||
E-Mail / SMTP-Server für Aufgaben-Alarme
|
||||
</h2>
|
||||
<p className="text-zinc-400 mt-0.5">
|
||||
<p className="text-muted mt-0.5">
|
||||
Konfiguriere den Postausgangsserver per Docker-Compose Environment-Variablen oder
|
||||
direkt hier über die Oberfläche (wird persistent in der SQLite-Datenbank gespeichert).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{settings.smtpConfiguredViaEnv && (
|
||||
<span className="px-2.5 py-1 bg-emerald-950/50 border border-emerald-500/40 text-emerald-300 text-[11px] flex-shrink-0">
|
||||
<span className="badge-ok text-[11px] flex-shrink-0">
|
||||
Docker Env erkannt
|
||||
</span>
|
||||
)}
|
||||
@@ -1182,7 +1180,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-zinc-300 font-medium mb-1">
|
||||
<label className="block text-muted font-medium mb-1">
|
||||
SMTP Host (`SMTP_HOST`)
|
||||
</label>
|
||||
<input
|
||||
@@ -1191,13 +1189,13 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
onChange={(e) =>
|
||||
setSettings((prev) => ({ ...prev, smtpHost: e.target.value }))
|
||||
}
|
||||
placeholder="z.B. smtp.gmail.com oder mail.deinedomain.de"
|
||||
className="w-full h-9 px-3 input-field text-white font-mono"
|
||||
placeholder="z.B. smtp.example.com oder mail.deinedomain.de"
|
||||
className="w-full h-9 px-3 input-field font-mono"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-zinc-300 font-medium mb-1">
|
||||
<label className="block text-muted font-medium mb-1">
|
||||
SMTP Port (`SMTP_PORT`)
|
||||
</label>
|
||||
<input
|
||||
@@ -1207,12 +1205,12 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
setSettings((prev) => ({ ...prev, smtpPort: Number(e.target.value) }))
|
||||
}
|
||||
placeholder="587"
|
||||
className="w-full h-9 px-3 input-field text-white font-mono"
|
||||
className="w-full h-9 px-3 input-field font-mono"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-zinc-300 font-medium mb-1">
|
||||
<label className="block text-muted font-medium mb-1">
|
||||
SMTP Benutzername (`SMTP_USER`)
|
||||
</label>
|
||||
<input
|
||||
@@ -1222,12 +1220,12 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
setSettings((prev) => ({ ...prev, smtpUser: e.target.value }))
|
||||
}
|
||||
placeholder="alerts@deinedomain.de"
|
||||
className="w-full h-9 px-3 input-field text-white font-mono"
|
||||
className="w-full h-9 px-3 input-field font-mono"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-zinc-300 font-medium mb-1">
|
||||
<label className="block text-muted font-medium mb-1">
|
||||
SMTP Passwort (`SMTP_PASS`)
|
||||
</label>
|
||||
<input
|
||||
@@ -1237,12 +1235,12 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
setSettings((prev) => ({ ...prev, smtpPass: e.target.value }))
|
||||
}
|
||||
placeholder="••••••••••••"
|
||||
className="w-full h-9 px-3 input-field text-white font-mono"
|
||||
className="w-full h-9 px-3 input-field font-mono"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-zinc-300 font-medium mb-1">
|
||||
<label className="block text-muted font-medium mb-1">
|
||||
Absender-Adresse (`SMTP_FROM`)
|
||||
</label>
|
||||
<input
|
||||
@@ -1252,7 +1250,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
setSettings((prev) => ({ ...prev, smtpFrom: e.target.value }))
|
||||
}
|
||||
placeholder="MindSift <alerts@deinedomain.de>"
|
||||
className="w-full h-9 px-3 input-field text-white"
|
||||
className="w-full h-9 px-3 input-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1264,9 +1262,9 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
onChange={(e) =>
|
||||
setSettings((prev) => ({ ...prev, smtpTls: e.target.checked }))
|
||||
}
|
||||
className="w-4 h-4 accent-blue-600"
|
||||
className="w-4 h-4 checkbox-square cursor-pointer"
|
||||
/>
|
||||
<span className="text-white font-medium">
|
||||
<span className="text-main font-medium">
|
||||
STARTTLS / TLS-Verschlüsselung verwenden (`SMTP_TLS=true`)
|
||||
</span>
|
||||
</label>
|
||||
@@ -1284,9 +1282,9 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Test SMTP Delivery */}
|
||||
<div className="p-4 bg-canvas border border-border space-y-2">
|
||||
<h3 className="font-bold text-white">SMTP-Verbindung testen</h3>
|
||||
<p className="text-[11px] text-zinc-400">
|
||||
<div className="p-4 bg-canvas border border-border rounded-card space-y-2">
|
||||
<h3 className="font-bold text-main">SMTP-Verbindung testen</h3>
|
||||
<p className="text-[11px] text-muted">
|
||||
Sendet sofort eine Test-E-Mail mit den oben eingetragenen Daten.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
@@ -1295,7 +1293,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
||||
value={testSmtpEmail}
|
||||
onChange={(e) => setTestSmtpEmail(e.target.value)}
|
||||
placeholder="empfaenger@beispiel.de"
|
||||
className="flex-1 h-8 px-3 input-field text-white"
|
||||
className="flex-1 h-8 px-3 input-field"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -14,20 +14,26 @@ import {
|
||||
Globe,
|
||||
Send,
|
||||
AlertCircle,
|
||||
Palette,
|
||||
} from 'lucide-react';
|
||||
import { APIKey, User } from '../../lib/types';
|
||||
import { api, triggerHaptic } from '../../lib/api';
|
||||
import { AVAILABLE_THEMES, ThemeId } from '../../lib/theme';
|
||||
|
||||
interface UserSettingsViewProps {
|
||||
currentUser: User;
|
||||
activeTheme: ThemeId;
|
||||
onThemeChange: (themeId: ThemeId) => void;
|
||||
onUserUpdated: (u: User) => void;
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
type UserSettingsSection = 'profile' | 'alarms' | 'webhooks';
|
||||
type UserSettingsSection = 'profile' | 'appearance' | 'alarms' | 'webhooks';
|
||||
|
||||
export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
currentUser,
|
||||
activeTheme,
|
||||
onThemeChange,
|
||||
onUserUpdated,
|
||||
onBack,
|
||||
}) => {
|
||||
@@ -219,7 +225,8 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
};
|
||||
|
||||
const baseUrl = window.location.origin;
|
||||
const activeKeyPlaceholder = newlyCreatedKey || apiKeys[0]?.keyPrefix + '••••••••' || 'DEIN_API_KEY';
|
||||
const activeKeyPlaceholder =
|
||||
newlyCreatedKey || (apiKeys[0]?.keyPrefix ? apiKeys[0].keyPrefix + '••••••••' : 'DEIN_API_KEY');
|
||||
|
||||
const curlTaskExample = `curl -X POST ${baseUrl}/api/v1/inbox \\
|
||||
-H "X-API-Key: ${activeKeyPlaceholder}" \\
|
||||
@@ -247,14 +254,14 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
<span>Zurück</span>
|
||||
</button>
|
||||
<div>
|
||||
<h1 className="text-base font-bold text-white tracking-tight">Kontoeinstellungen</h1>
|
||||
<p className="text-xs text-zinc-400">
|
||||
Persönliches Profil, Aufgaben-Alarme, Private Webhooks & Daten-Backup
|
||||
<h1 className="text-base font-bold text-main tracking-tight">Kontoeinstellungen</h1>
|
||||
<p className="text-xs text-muted">
|
||||
Persönliches Profil, Design-Themes, Aufgaben-Alarme, Private Webhooks & Daten-Backup
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className="px-2.5 py-1 bg-panel border border-border text-xs text-zinc-300 font-medium">
|
||||
<span className="px-2.5 py-1 bg-panel border border-border text-xs text-muted font-medium">
|
||||
Rolle: <strong className="text-accent">{roleBadgeLabel}</strong>
|
||||
</span>
|
||||
</div>
|
||||
@@ -262,19 +269,34 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
{/* Main 2-Column Layout */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 items-start">
|
||||
{/* Left Navigation Sidebar */}
|
||||
<aside className="bg-panel border border-border divide-y divide-border">
|
||||
<aside className="panel-surface divide-y divide-border overflow-hidden">
|
||||
<button
|
||||
onClick={() => setSection('profile')}
|
||||
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||
section === 'profile'
|
||||
? 'bg-panel-elevated text-white border-l-2 border-l-accent'
|
||||
: 'text-zinc-400 hover:text-white hover:bg-white/[0.02]'
|
||||
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||
: 'text-muted hover:text-main hover:bg-panel-hover'
|
||||
}`}
|
||||
>
|
||||
<UserIcon className="w-4 h-4 text-accent flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-semibold">Profil & Sicherheit</p>
|
||||
<p className="text-[10px] text-zinc-500">Name, E-Mail & Passwort</p>
|
||||
<p className="text-[10px] text-faint">Name, E-Mail & Passwort</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setSection('appearance')}
|
||||
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||
section === 'appearance'
|
||||
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||
: 'text-muted hover:text-main hover:bg-panel-hover'
|
||||
}`}
|
||||
>
|
||||
<Palette className="w-4 h-4 text-accent flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-semibold">Darstellung & Themes</p>
|
||||
<p className="text-[10px] text-faint">Farben, Kontraste & Rundungen</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -282,14 +304,14 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
onClick={() => setSection('alarms')}
|
||||
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||
section === 'alarms'
|
||||
? 'bg-panel-elevated text-white border-l-2 border-l-accent'
|
||||
: 'text-zinc-400 hover:text-white hover:bg-white/[0.02]'
|
||||
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||
: 'text-muted hover:text-main hover:bg-panel-hover'
|
||||
}`}
|
||||
>
|
||||
<Bell className="w-4 h-4 text-accent flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-semibold">Alarme</p>
|
||||
<p className="text-[10px] text-zinc-500">E-Mail, Discord & Webhooks</p>
|
||||
<p className="text-[10px] text-faint">E-Mail, Discord & Webhooks</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -297,44 +319,42 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
onClick={() => setSection('webhooks')}
|
||||
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||
section === 'webhooks'
|
||||
? 'bg-panel-elevated text-white border-l-2 border-l-accent'
|
||||
: 'text-zinc-400 hover:text-white hover:bg-white/[0.02]'
|
||||
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||
: 'text-muted hover:text-main hover:bg-panel-hover'
|
||||
}`}
|
||||
>
|
||||
<Key className="w-4 h-4 text-accent flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-semibold">Private Webhooks & Export</p>
|
||||
<p className="text-[10px] text-zinc-500">API-Keys, Curl & JSON-Backup</p>
|
||||
<p className="text-[10px] text-faint">API-Keys, Curl & JSON-Backup</p>
|
||||
</div>
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
{/* Right Content Area */}
|
||||
<div className="md:col-span-3 bg-panel border border-border p-5 text-xs">
|
||||
<div className="md:col-span-3 panel-surface p-5 text-xs">
|
||||
{/* ================================================================= */}
|
||||
{/* SECTION 1: PROFIL & SICHERHEIT */}
|
||||
{/* ================================================================= */}
|
||||
{section === 'profile' && (
|
||||
<div className="space-y-5 max-w-2xl">
|
||||
<div className="border-b border-border pb-3">
|
||||
<h2 className="text-sm font-bold text-white">Profil & Anmeldedaten</h2>
|
||||
<p className="text-zinc-400 mt-0.5">
|
||||
<h2 className="text-sm font-bold text-main">Profil & Anmeldedaten</h2>
|
||||
<p className="text-muted mt-0.5">
|
||||
Verwalte deinen Anzeigenamen, deine E-Mail-Adresse und dein persönliches Passwort.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{profileMsg && (
|
||||
<div
|
||||
className={`p-3 border flex items-center gap-2 ${
|
||||
profileMsg.type === 'ok'
|
||||
? 'bg-blue-950/30 border-accent text-blue-200'
|
||||
: 'bg-red-950/30 border-red-500 text-red-200'
|
||||
className={`flex items-center gap-2 ${
|
||||
profileMsg.type === 'ok' ? 'alert-ok' : 'alert-err'
|
||||
}`}
|
||||
>
|
||||
{profileMsg.type === 'ok' ? (
|
||||
<Check className="w-4 h-4 text-accent flex-shrink-0" />
|
||||
<Check className="w-4 h-4 text-status-ok flex-shrink-0" />
|
||||
) : (
|
||||
<AlertCircle className="w-4 h-4 text-red-400 flex-shrink-0" />
|
||||
<AlertCircle className="w-4 h-4 text-status-err flex-shrink-0" />
|
||||
)}
|
||||
<span>{profileMsg.text}</span>
|
||||
</div>
|
||||
@@ -343,30 +363,32 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
<form onSubmit={handleSaveProfile} className="space-y-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-zinc-300 font-medium mb-1">Anzeigename</label>
|
||||
<label className="block text-muted font-medium mb-1">Anzeigename</label>
|
||||
<input
|
||||
type="text"
|
||||
value={displayName}
|
||||
onChange={(e) => setDisplayName(e.target.value)}
|
||||
required
|
||||
className="w-full h-9 px-3 input-field text-white"
|
||||
className="w-full h-9 px-3 input-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-zinc-300 font-medium mb-1">E-Mail-Adresse (Login)</label>
|
||||
<label className="block text-muted font-medium mb-1">
|
||||
E-Mail-Adresse (Login)
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className="w-full h-9 px-3 input-field text-white"
|
||||
className="w-full h-9 px-3 input-field"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-zinc-300 font-medium mb-1">
|
||||
<label className="block text-muted font-medium mb-1">
|
||||
Neues Passwort (optional — leer lassen für keine Änderung)
|
||||
</label>
|
||||
<input
|
||||
@@ -374,7 +396,7 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
placeholder="Mindestens 4 Zeichen"
|
||||
className="w-full h-9 px-3 input-field text-white"
|
||||
className="w-full h-9 px-3 input-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -392,30 +414,129 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
)}
|
||||
|
||||
{/* ================================================================= */}
|
||||
{/* SECTION 2: ALARME (MULTI-CHANNEL: EMAIL, DISCORD, CUSTOM WEBHOOK) */}
|
||||
{/* SECTION 2: DARSTELLUNG & THEMES (SINGLE-FILE CSS THEMING) */}
|
||||
{/* ================================================================= */}
|
||||
{section === 'appearance' && (
|
||||
<div className="space-y-5">
|
||||
<div className="border-b border-border pb-3">
|
||||
<h2 className="text-sm font-bold text-main">
|
||||
Darstellung & Single-File CSS Themes
|
||||
</h2>
|
||||
<p className="text-muted mt-0.5">
|
||||
Jedes Theme ist eine einzige, eigenständige CSS-Datei, in der alle Farben,
|
||||
Oberflächen, Kontraste und Ecken-Rundungen in 6 festen Gruppen gebündelt sind.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{AVAILABLE_THEMES.map((t) => {
|
||||
const isSelected = activeTheme === t.id;
|
||||
return (
|
||||
<button
|
||||
key={t.id}
|
||||
type="button"
|
||||
onClick={() => onThemeChange(t.id)}
|
||||
className={`p-4 text-left border transition-all flex flex-col justify-between gap-3 ${
|
||||
isSelected
|
||||
? 'bg-canvas border-accent shadow-sm'
|
||||
: 'bg-canvas border-border hover:border-strong'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div>
|
||||
<p className="font-bold text-sm text-main flex items-center gap-2">
|
||||
<span>{t.name}</span>
|
||||
{isSelected && <span className="badge-accent">Aktiv</span>}
|
||||
</p>
|
||||
<p className="text-xs text-muted mt-0.5">{t.subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mini Color & Geometry Swatch */}
|
||||
<div className="flex items-center justify-between pt-2 border-t border-border">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span
|
||||
className="w-5 h-5 border border-strong inline-block"
|
||||
style={{
|
||||
backgroundColor: t.previewBg,
|
||||
borderRadius: t.isRounded ? '6px' : '0px',
|
||||
}}
|
||||
title="Canvas-Hintergrund"
|
||||
/>
|
||||
<span
|
||||
className="w-5 h-5 border border-strong inline-block"
|
||||
style={{
|
||||
backgroundColor: t.previewPanel,
|
||||
borderRadius: t.isRounded ? '6px' : '0px',
|
||||
}}
|
||||
title="Panel-Oberfläche"
|
||||
/>
|
||||
<span
|
||||
className="w-5 h-5 border border-strong inline-block"
|
||||
style={{
|
||||
backgroundColor: t.previewAccent,
|
||||
borderRadius: t.isRounded ? '6px' : '0px',
|
||||
}}
|
||||
title="Akzentfarbe"
|
||||
/>
|
||||
<span
|
||||
className="w-5 h-5 border border-strong inline-block"
|
||||
style={{
|
||||
backgroundColor: t.previewText,
|
||||
borderRadius: t.isRounded ? '6px' : '0px',
|
||||
}}
|
||||
title="Textfarbe"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<code className="text-[10px] font-mono text-faint">{t.file}</code>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="p-3.5 bg-canvas border border-border space-y-1.5">
|
||||
<p className="font-semibold text-main">Eigenes Theme anpassen oder hinzufügen:</p>
|
||||
<p className="text-muted text-[11px] leading-relaxed">
|
||||
Alle Theme-Dateien liegen unter{' '}
|
||||
<code className="font-mono text-accent">frontend/src/themes/</code> (inklusive der
|
||||
Kopiervorlage{' '}
|
||||
<code className="font-mono text-accent">theme-template.css</code>). Dort sind alle
|
||||
Eigenschaften in 6 Blöcken gruppiert: <strong>1. Akzentfarbe</strong>,{' '}
|
||||
<strong>2. Oberflächen</strong>, <strong>3. Textfarben</strong>,{' '}
|
||||
<strong>4. Rahmen & Schatten</strong>, <strong>5. Statusfarben</strong> und{' '}
|
||||
<strong>6. Rundungen</strong>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ================================================================= */}
|
||||
{/* SECTION 3: ALARME (MULTI-CHANNEL: EMAIL, DISCORD, CUSTOM WEBHOOK) */}
|
||||
{/* ================================================================= */}
|
||||
{section === 'alarms' && (
|
||||
<div className="space-y-5">
|
||||
<div className="border-b border-border pb-3">
|
||||
<h2 className="text-sm font-bold text-white">Aufgaben-Alarme & Benachrichtigungsziele</h2>
|
||||
<p className="text-zinc-400 mt-0.5">
|
||||
Wähle aus, über welche Kanäle du benachrichtigt werden möchtest, sobald eine Aufgabe
|
||||
mit Fälligkeitsdatum ansteht. Mehrere Ziele können gleichzeitig aktiv sein.
|
||||
<h2 className="text-sm font-bold text-main">
|
||||
Aufgaben-Alarme & Benachrichtigungsziele
|
||||
</h2>
|
||||
<p className="text-muted mt-0.5">
|
||||
Wähle aus, über welche Kanäle du benachrichtigt werden möchtest, sobald eine
|
||||
Aufgabe mit Fälligkeitsdatum ansteht. Mehrere Ziele können gleichzeitig aktiv sein.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{alarmMsg && (
|
||||
<div
|
||||
className={`p-3 border flex items-center gap-2 ${
|
||||
alarmMsg.type === 'ok'
|
||||
? 'bg-blue-950/30 border-accent text-blue-200'
|
||||
: 'bg-red-950/30 border-red-500 text-red-200'
|
||||
className={`flex items-center gap-2 ${
|
||||
alarmMsg.type === 'ok' ? 'alert-ok' : 'alert-err'
|
||||
}`}
|
||||
>
|
||||
{alarmMsg.type === 'ok' ? (
|
||||
<Check className="w-4 h-4 text-accent flex-shrink-0" />
|
||||
<Check className="w-4 h-4 text-status-ok flex-shrink-0" />
|
||||
) : (
|
||||
<AlertCircle className="w-4 h-4 text-red-400 flex-shrink-0" />
|
||||
<AlertCircle className="w-4 h-4 text-status-err flex-shrink-0" />
|
||||
)}
|
||||
<span>{alarmMsg.text}</span>
|
||||
</div>
|
||||
@@ -425,17 +546,17 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
{/* Lead Time Selector */}
|
||||
<div className="p-3.5 bg-canvas border border-border flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||
<div>
|
||||
<label className="font-semibold text-white block">
|
||||
<label className="font-semibold text-main block">
|
||||
Vorwarnzeit vor Fälligkeit
|
||||
</label>
|
||||
<p className="text-[11px] text-zinc-400">
|
||||
<p className="text-[11px] text-muted">
|
||||
Wann soll MindSift den Alarm vor dem Fälligkeitszeitpunkt auslösen?
|
||||
</p>
|
||||
</div>
|
||||
<select
|
||||
value={notifyBeforeMinutes}
|
||||
onChange={(e) => setNotifyBeforeMinutes(Number(e.target.value))}
|
||||
className="h-9 px-3 input-field text-white bg-panel min-w-[200px]"
|
||||
className="h-9 px-3 input-field min-w-[200px]"
|
||||
>
|
||||
<option value={0}>Pünktlich bei Fälligkeit (0 Min.)</option>
|
||||
<option value={15}>15 Minuten vorher</option>
|
||||
@@ -448,10 +569,8 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
|
||||
{/* Channel 1: E-Mail */}
|
||||
<div
|
||||
className={`p-4 border transition-colors space-y-3 ${
|
||||
alarmChannels.includes('email')
|
||||
? 'bg-canvas border-accent'
|
||||
: 'bg-canvas/60 border-border'
|
||||
className={`p-4 border transition-colors space-y-3 bg-canvas ${
|
||||
alarmChannels.includes('email') ? 'border-accent' : 'border-border'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
@@ -464,8 +583,8 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
/>
|
||||
<Mail className="w-4 h-4 text-accent" />
|
||||
<div>
|
||||
<span className="font-bold text-white block">Ziel: E-Mail</span>
|
||||
<span className="text-[11px] text-zinc-400">
|
||||
<span className="font-bold text-main block">Ziel: E-Mail</span>
|
||||
<span className="text-[11px] text-muted">
|
||||
Sendet eine E-Mail über den konfigurierten SMTP-Server der Instanz.
|
||||
</span>
|
||||
</div>
|
||||
@@ -483,7 +602,7 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="pl-6">
|
||||
<label className="block text-zinc-400 mb-1">
|
||||
<label className="block text-muted mb-1">
|
||||
Abweichende Empfänger-E-Mail (optional, Standard: {currentUser.email})
|
||||
</label>
|
||||
<input
|
||||
@@ -491,17 +610,15 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
value={alarmEmail}
|
||||
onChange={(e) => setAlarmEmail(e.target.value)}
|
||||
placeholder={currentUser.email}
|
||||
className="w-full h-8 px-2.5 input-field text-white"
|
||||
className="w-full h-8 px-2.5 input-field"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Channel 2: Discord Webhook */}
|
||||
<div
|
||||
className={`p-4 border transition-colors space-y-3 ${
|
||||
alarmChannels.includes('discord')
|
||||
? 'bg-canvas border-accent'
|
||||
: 'bg-canvas/60 border-border'
|
||||
className={`p-4 border transition-colors space-y-3 bg-canvas ${
|
||||
alarmChannels.includes('discord') ? 'border-accent' : 'border-border'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
@@ -514,9 +631,10 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
/>
|
||||
<MessageSquare className="w-4 h-4 text-accent" />
|
||||
<div>
|
||||
<span className="font-bold text-white block">Ziel: Discord Webhook</span>
|
||||
<span className="text-[11px] text-zinc-400">
|
||||
Sendet eine formatierte Embed-Benachrichtigung direkt in deinen Discord-Kanal.
|
||||
<span className="font-bold text-main block">Ziel: Discord Webhook</span>
|
||||
<span className="text-[11px] text-muted">
|
||||
Sendet eine formatierte Embed-Benachrichtigung direkt in deinen
|
||||
Discord-Kanal.
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
@@ -533,23 +651,21 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="pl-6">
|
||||
<label className="block text-zinc-400 mb-1">Discord Webhook URL</label>
|
||||
<label className="block text-muted mb-1">Discord Webhook URL</label>
|
||||
<input
|
||||
type="url"
|
||||
value={discordWebhookUrl}
|
||||
onChange={(e) => setDiscordWebhookUrl(e.target.value)}
|
||||
placeholder="https://discord.com/api/webhooks/123456789/token..."
|
||||
className="w-full h-8 px-2.5 input-field text-white font-mono"
|
||||
className="w-full h-8 px-2.5 input-field font-mono"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Channel 3: Custom HTTP Webhook */}
|
||||
<div
|
||||
className={`p-4 border transition-colors space-y-3 ${
|
||||
alarmChannels.includes('webhook')
|
||||
? 'bg-canvas border-accent'
|
||||
: 'bg-canvas/60 border-border'
|
||||
className={`p-4 border transition-colors space-y-3 bg-canvas ${
|
||||
alarmChannels.includes('webhook') ? 'border-accent' : 'border-border'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
@@ -562,11 +678,12 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
/>
|
||||
<Globe className="w-4 h-4 text-accent" />
|
||||
<div>
|
||||
<span className="font-bold text-white block">
|
||||
<span className="font-bold text-main block">
|
||||
Ziel: Eigener Webhook (HTTP POST JSON)
|
||||
</span>
|
||||
<span className="text-[11px] text-zinc-400">
|
||||
Für eigene Empfänger-Skripte, Ntfy, Gotify, Slack, Mattermost oder Automatisierungen.
|
||||
<span className="text-[11px] text-muted">
|
||||
Für eigene Empfänger-Skripte, Ntfy, Gotify, Slack, Mattermost oder
|
||||
Automatisierungen.
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
@@ -583,13 +700,15 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="pl-6">
|
||||
<label className="block text-zinc-400 mb-1">Ziel-URL (empfängt JSON-Payload per POST)</label>
|
||||
<label className="block text-muted mb-1">
|
||||
Ziel-URL (empfängt JSON-Payload per POST)
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
value={customWebhookUrl}
|
||||
onChange={(e) => setCustomWebhookUrl(e.target.value)}
|
||||
placeholder="https://dein-server.local/webhook/mindsift-alarm"
|
||||
className="w-full h-8 px-2.5 input-field text-white font-mono"
|
||||
className="w-full h-8 px-2.5 input-field font-mono"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -608,15 +727,17 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
)}
|
||||
|
||||
{/* ================================================================= */}
|
||||
{/* SECTION 3: PRIVATE WEBHOOKS & EXPORT */}
|
||||
{/* SECTION 4: PRIVATE WEBHOOKS & EXPORT */}
|
||||
{/* ================================================================= */}
|
||||
{section === 'webhooks' && (
|
||||
<div className="space-y-5">
|
||||
<div className="border-b border-border pb-3">
|
||||
<h2 className="text-sm font-bold text-white">Private Webhooks (Eingehend) & Daten-Export</h2>
|
||||
<p className="text-zinc-400 mt-0.5">
|
||||
Erstelle persönliche API-Schlüssel, um aus externen Skripten, Server-Monitoring oder
|
||||
Discord-Bots automatisch Aufgaben und Gedanken in dein MindSift einzuspeisen.
|
||||
<h2 className="text-sm font-bold text-main">
|
||||
Private Webhooks (Eingehend) & Daten-Export
|
||||
</h2>
|
||||
<p className="text-muted mt-0.5">
|
||||
Erstelle persönliche API-Schlüssel, um aus externen Skripten, Server-Monitoring
|
||||
oder Discord-Bots automatisch Aufgaben und Gedanken in dein MindSift einzuspeisen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -627,7 +748,7 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
value={newKeyName}
|
||||
onChange={(e) => setNewKeyName(e.target.value)}
|
||||
placeholder="Bezeichnung des Schlüssels (z.B. Proxmox Monitoring, Discord-Forwarder)…"
|
||||
className="flex-1 h-9 px-3 input-field text-white"
|
||||
className="flex-1 h-9 px-3 input-field"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
@@ -639,12 +760,13 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
</form>
|
||||
|
||||
{newlyCreatedKey && (
|
||||
<div className="p-3 bg-accent/15 border border-accent flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
||||
<div className="p-3 bg-accent-dim border border-accent flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<p className="text-[11px] text-accent font-bold">
|
||||
Neuer API-Schlüssel erstellt (bitte jetzt kopieren, wird nur einmal im Klartext angezeigt):
|
||||
Neuer API-Schlüssel erstellt (bitte jetzt kopieren, wird nur einmal im
|
||||
Klartext angezeigt):
|
||||
</p>
|
||||
<code className="text-xs font-mono text-white break-all">{newlyCreatedKey}</code>
|
||||
<code className="text-xs font-mono text-main break-all">{newlyCreatedKey}</code>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleCopy(newlyCreatedKey, 'key')}
|
||||
@@ -658,16 +780,17 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
{/* Existing Keys List */}
|
||||
<div className="border border-border divide-y divide-border bg-canvas">
|
||||
{apiKeys.length === 0 ? (
|
||||
<div className="p-4 text-center text-zinc-500">
|
||||
<div className="p-4 text-center text-faint">
|
||||
Noch keine persönlichen API-Schlüssel vorhanden.
|
||||
</div>
|
||||
) : (
|
||||
apiKeys.map((k) => (
|
||||
<div key={k.id} className="p-3 flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p className="font-semibold text-white">{k.name}</p>
|
||||
<p className="text-[11px] text-zinc-500 font-mono">
|
||||
Präfix: {k.keyPrefix}•••••••• · Erstellt: {new Date(k.createdAt).toLocaleDateString()}
|
||||
<p className="font-semibold text-main">{k.name}</p>
|
||||
<p className="text-[11px] text-faint font-mono">
|
||||
Präfix: {k.keyPrefix}•••••••• · Erstellt:{' '}
|
||||
{new Date(k.createdAt).toLocaleDateString()}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
@@ -685,7 +808,7 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
{/* Curl Example */}
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-semibold text-zinc-300 flex items-center gap-1.5">
|
||||
<span className="font-semibold text-muted flex items-center gap-1.5">
|
||||
<Terminal className="w-3.5 h-3.5 text-accent" />
|
||||
<span>Curl-Beispiel: Direkt als Aufgabe einspeisen</span>
|
||||
</span>
|
||||
@@ -696,7 +819,7 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
{copiedLabel === 'curl' ? 'Kopiert!' : 'Befehl kopieren'}
|
||||
</button>
|
||||
</div>
|
||||
<pre className="p-3 bg-canvas border border-border text-[11px] font-mono text-zinc-300 overflow-x-auto whitespace-pre">
|
||||
<pre className="p-3 code-block text-[11px] font-mono overflow-x-auto whitespace-pre">
|
||||
{curlTaskExample}
|
||||
</pre>
|
||||
</div>
|
||||
@@ -704,9 +827,10 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
{/* Full Data Backup */}
|
||||
<div className="pt-4 border-t border-border flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 className="font-bold text-white">Vollständiger Daten-Export (JSON Backup)</h3>
|
||||
<p className="text-zinc-400 text-[11px]">
|
||||
Lädt alle deine Gedanken, Aufgaben, Markdown-Dokumente und Listen als portable JSON-Datei herunter.
|
||||
<h3 className="font-bold text-main">Vollständiger Daten-Export (JSON Backup)</h3>
|
||||
<p className="text-muted text-[11px]">
|
||||
Lädt alle deine Gedanken, Aufgaben, Markdown-Dokumente und Listen als portable
|
||||
JSON-Datei herunter.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
|
||||
Reference in new issue
Block a user