Profilbild: zufälliger DiceBear-Avatar (Big Smile) pro Kunde
Der Server erzeugt das SVG lokal unter /api/avatar.svg (Seed = Kunden-ID), es geht also keine Anfrage an dicebear.com. Die Initialen bleiben als Fallback, bis das Bild geladen ist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e4efdac226
commit
8c9d28c30f
5 files changed
+70
-2
No files matched your search
Generated
+32
@@ -8,6 +8,8 @@
|
||||
"name": "webklar-kundenbereich",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@dicebear/big-smile": "^9.4.2",
|
||||
"@dicebear/core": "^9.4.3",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
@@ -20,6 +22,36 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/big-smile": {
|
||||
"version": "9.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/big-smile/-/big-smile-9.4.2.tgz",
|
||||
"integrity": "sha512-hmT5i7rcPPhStjZyg28pbIhdTnnMBzK3RObI0vKCpY30EFrzaPkkdDL6Ck5fAFBdvDIW1EpOJkenyR0XPmhgbQ==",
|
||||
"license": "(MIT AND CC-BY-4.0)",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/core": {
|
||||
"version": "9.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/core/-/core-9.4.3.tgz",
|
||||
"integrity": "sha512-9ITrQI57k3p5hKuU8HZJ1d0kPdrmTKEJDOdlKQ8CpaChbtmnD1R+dtWmNz5IesYThXM8F64pJ0ZvJ2rsY8Hciw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dicebear/big-smile": "^9.4.2",
|
||||
"@dicebear/core": "^9.4.3",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
|
||||
+20
-2
@@ -1920,6 +1920,20 @@ function showView(name, { updateHash = true } = {}) {
|
||||
document.dispatchEvent(new CustomEvent('kb:view', { detail: { name } }))
|
||||
}
|
||||
|
||||
/* Profilbild (DiceBear Big Smile vom eigenen Server); die Initialen bleiben stehen, bis das Bild geladen ist,
|
||||
und falls es nicht laedt. Die Kunden-ID im Query sorgt dafuer, dass "Als Kunde ansehen" nicht das gecachte Bild zeigt. */
|
||||
function setAvatar(el, initials, seed) {
|
||||
if (!el) return
|
||||
el.textContent = initials
|
||||
el.dataset.initials = initials
|
||||
if (!seed) return
|
||||
const img = new Image()
|
||||
img.alt = ''
|
||||
img.decoding = 'async'
|
||||
img.onload = () => { el.textContent = ''; el.appendChild(img); el.classList.add('has-img') }
|
||||
img.src = `/api/avatar.svg?c=${encodeURIComponent(seed)}`
|
||||
}
|
||||
|
||||
function initialsOf(name) {
|
||||
const words = String(name || '').split(/\s+/).filter((w) => /^[\p{L}\p{N}]/u.test(w))
|
||||
const letters = words.length > 1 ? words[0][0] + words[1][0] : (words[0] || '').slice(0, 2)
|
||||
@@ -1985,7 +1999,7 @@ async function initDashboardPage() {
|
||||
document.getElementById('customer-name').textContent = displayName
|
||||
document.getElementById('customer-meta').textContent =
|
||||
[customer.code ? `Kundennr. ${customer.code}` : '', customer.email].filter(Boolean).join(' · ')
|
||||
document.getElementById('customer-avatar').textContent = initialsOf(displayName)
|
||||
setAvatar(document.getElementById('customer-avatar'), initialsOf(displayName), customer.id)
|
||||
|
||||
api('/api/contact')
|
||||
.then((c) => {
|
||||
@@ -2212,7 +2226,11 @@ function initAccountSheet() {
|
||||
const fill = () => {
|
||||
document.getElementById('kb-sheet-name').textContent = document.getElementById('customer-name')?.textContent || 'Mein Konto'
|
||||
document.getElementById('kb-sheet-meta').textContent = document.getElementById('customer-meta')?.textContent || ''
|
||||
document.getElementById('kb-sheet-avatar').textContent = avatar.textContent
|
||||
const sheetAvatar = document.getElementById('kb-sheet-avatar')
|
||||
const img = avatar.querySelector('img')
|
||||
sheetAvatar.textContent = img ? '' : avatar.textContent
|
||||
sheetAvatar.classList.toggle('has-img', Boolean(img))
|
||||
if (img) sheetAvatar.appendChild(img.cloneNode())
|
||||
renderInstallOffers()
|
||||
}
|
||||
if (typeof sheet.showModal !== 'function') {
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
flex: none; width: 36px; height: 36px; border-radius: 50%; display: grid; place-items: center;
|
||||
font-size: 13px; font-weight: 700; background: #26124a; border: 1px solid var(--kb-purple-line);
|
||||
}
|
||||
.kb-avatar.has-img { overflow: hidden; }
|
||||
.kb-avatar img { width: 100%; height: 100%; display: block; border-radius: 50%; }
|
||||
|
||||
/* ---------------------------------------------------------------- Raster --- */
|
||||
.kb-shell {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Router } from 'express'
|
||||
import { createAvatar } from '@dicebear/core'
|
||||
import * as bigSmile from '@dicebear/big-smile'
|
||||
import { config } from '../config.js'
|
||||
import { getSessionCustomerId, requireSession } from '../middleware/session.js'
|
||||
import { getDocument, listDocuments, Query } from '../services/appwriteAdmin.js'
|
||||
@@ -105,4 +107,16 @@ router.get('/contact', requireSession, async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
/** Profilbild: DiceBear "Big Smile", lokal erzeugt (keine Anfrage an dicebear.com).
|
||||
Die Kunden-ID ist der Seed - zufaellig pro Kunde, aber bei jedem Login dasselbe Bild. */
|
||||
router.get('/avatar.svg', requireSession, (req, res) => {
|
||||
const svg = createAvatar(bigSmile, {
|
||||
seed: String(getSessionCustomerId(req) || 'webklar'),
|
||||
backgroundColor: ['b6e3f4', 'c0aede', 'd1d4f9', 'ffd5dc', 'ffdfbf'],
|
||||
}).toString()
|
||||
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8')
|
||||
res.setHeader('Cache-Control', 'private, max-age=86400')
|
||||
return res.send(svg)
|
||||
})
|
||||
|
||||
export default router
|
||||
Reference in new issue
Block a user