feat(profil): eigenes Profilbild hochladen
Jeder setzt sein Bild unter "Mein Konto", Vorgesetzte und Administratoren auch fuer die Benutzer, die sie verwalten. Wo bisher die Initialen standen, steht jetzt das Bild - Seitenleiste, Kopfzeile, Team, Dashboard, Reports, Projekte, Kunden und die Buchungsliste. Ohne Bild bleiben die Initialen. Der Browser verkleinert das Bild vor dem Hochladen auf ein Quadrat von 256 px und schneidet mittig zu (lib/imageCompress.ts): aus einem 4-MB-Foto werden wenige Kilobyte, und der Server muss kein Bild verarbeiten. Gespeichert wird es als Data-URL in pulse_users.avatar (LONGTEXT). Kein Upload-Verzeichnis, das gesichert und beim Deployment am Leben gehalten werden muesste. Geprueft wird serverseitig in lib/avatar.js: nur PNG, JPEG, GIF, WebP und AVIF als Data-URL, hoechstens 2 MB. SVG ist bewusst nicht erlaubt, es kann Skripte tragen. Externe http(s)-Adressen ebenso wenig - sonst laedt jeder Seitenaufruf ein Bild von einem fremden Server und verraet, wer wann mit Pulse arbeitet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2d3d843988
commit
9576d18abc
@@ -311,7 +311,7 @@ export const ProjectDetail: React.FC<Props> = ({ project: p, canEdit, users, onC
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{p.members.map((m) => (
|
||||
<span key={m.id} className="flex items-center gap-2 rounded-full border border-ink-200 py-1 pl-1 pr-3 text-xs font-bold text-ink-700">
|
||||
<Avatar id={m.id} name={m.name} color={m.color} size={22} external={m.is_external} />
|
||||
<Avatar id={m.id} name={m.name} color={m.color} avatar={m.avatar} size={22} external={m.is_external} />
|
||||
{m.name}
|
||||
</span>
|
||||
))}
|
||||
@@ -446,7 +446,7 @@ const MembersDialog: React.FC<{ project: Project; users: DirectoryUser[]; onClos
|
||||
return (
|
||||
<label key={u.id} className={`flex cursor-pointer items-center gap-3 rounded-xl border px-3 py-2 transition ${checked ? 'border-brand-300 bg-brand-50' : 'border-transparent hover:bg-ink-50'}`}>
|
||||
<input type="checkbox" checked={checked} onChange={() => setIds(checked ? ids.filter((id) => id !== u.id) : [...ids, u.id])} />
|
||||
<Avatar id={u.id} name={u.name} color={u.color} size={30} external={u.is_external} />
|
||||
<Avatar id={u.id} name={u.name} color={u.color} avatar={u.avatar} size={30} external={u.is_external} />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate text-sm font-bold text-ink-800">{u.name}</span>
|
||||
<span className="block truncate text-xs text-ink-400">{u.is_external ? `Extern · ${u.company ?? ''}` : u.job_title ?? ''}</span>
|
||||
|
||||
Reference in New Issue
Block a user