diff --git a/public/funktionen.js b/public/funktionen.js
index 082aa5d..ff130aa 100644
--- a/public/funktionen.js
+++ b/public/funktionen.js
@@ -100,7 +100,7 @@ function newsTile(projects) {
/* Analyse: Kachel je nach Zustand der Websites (comingSoon = Schalter aus) */
const analyseOf = (project) => project.analyse || { comingSoon: true }
-const analyseState = (a) => (a.comingSoon ? 'soon' : a.state === 'active' ? 'active' : a.eligible ? 'off' : 'blocked')
+const analyseState = (a) => (a.comingSoon ? 'soon' : a.state === 'unknown' ? 'unknown' : a.state === 'disabled' ? 'disabled' : a.state === 'active' ? 'active' : a.eligible ? 'off' : 'blocked')
const ANALYSE_TEXT = 'Sieh, wie viele Besucher deine Website hat und was sie sich ansehen.'
function pickAnalyseProject(projects) {
@@ -113,9 +113,12 @@ function analyseTile(projects) {
if (!target || a.comingSoon) return soonTile('chart', 'Analyse', ANALYSE_TEXT)
const state = analyseState(a)
const active = projects.filter((p) => analyseState(analyseOf(p)) === 'active').length
- const pill = state === 'active' ? ['Aktiv', 'on'] : ['Aus', 'off']
+ const pill = state === 'active' ? ['Aktiv', 'on'] : state === 'disabled' ? ['Gesperrt', 'off'] : ['Aus', 'off']
let action
- if (state === 'active') action = ``
+ let text = `${ANALYSE_TEXT} Ohne Cookies, auf einem Server in Deutschland.`
+ if (state === 'unknown') action = '
Die Daten konnten gerade nicht geladen werden.
'
+ else if (state === 'disabled') { text = 'Bitte melde dich bei uns'; action = '' }
+ else if (state === 'active') action = ``
else if (state === 'off') action = ``
else if (a.reason === 'kein_abo') action = 'Im Wartungs-Abo enthalten.
'
else action = `${esc(a.reasonText || 'Für diese Website gerade nicht verfügbar.')}
`
@@ -124,7 +127,7 @@ function analyseTile(projects) {
${svg('chart')}${pill[0]}
Analyse
- ${ANALYSE_TEXT} Ohne Cookies, auf einem Server in Deutschland.
+ ${text}
${count}
${action}
`
@@ -147,7 +150,7 @@ export async function initFunktionen(root) {
${analyseTile(projects)}
`
root.querySelector('[data-fn-tile="news"] .fn-tile-cta')?.addEventListener('click', (ev) => { selection.projectId = ev.currentTarget.dataset.project })
- root.querySelector('[data-fn-tile="analyse"] .fn-tile-cta')?.addEventListener('click', (ev) => { analyseSel.projectId = ev.currentTarget.dataset.project })
+ root.querySelector('[data-fn-tile="analyse"] .fn-tile-cta[data-project]')?.addEventListener('click', (ev) => { analyseSel.projectId = ev.currentTarget.dataset.project })
}
/* ---------------------------------------------------------------- News --- */
@@ -576,6 +579,7 @@ export async function initAnalyse(root, { projectId } = {}) {
const reload = () => initAnalyse(root).catch((err) => { root.innerHTML = `${esc(err.message)}
` })
if (a.state === 'active') renderAnalyseStats(stage, project, reload)
else if (a.state === 'disabled') stage.innerHTML = 'Analyse ist abgeschaltet
Für diese Website ist die Analyse im Moment gesperrt. , dann klären wir das.
'
+ else if (a.state === 'unknown') stage.innerHTML = 'Die Daten konnten gerade nicht geladen werden.
'
else if (!a.eligible) stage.innerHTML = `Analyse nicht verfügbar
${esc(a.reasonText || 'Für diese Website gerade nicht verfügbar.')}${a.reason === 'kein_abo' ? ' ' : ''}
`
else renderAnalyseActivation(stage, project, reload)
}
@@ -670,7 +674,9 @@ function barList(title, items, labelOf, { html = false } = {}) {
const COUNTRY_ROW = (key) => `${flagEmoji(key) ? `${flagEmoji(key)} ` : ''}${esc(countryName(key))}`
+let statsRequestId = 0
async function renderAnalyseStats(stage, project, reload) {
+ const myRequest = ++statsRequestId
const range = [7, 30, 90].includes(analyseSel.range) ? analyseSel.range : 30
const pid = project.projectId
stage.innerHTML = `
@@ -687,11 +693,11 @@ async function renderAnalyseStats(stage, project, reload) {
try {
stats = await request(`/api/funktionen/analyse/stats?projectId=${encodeURIComponent(pid)}&range=${range}`)
} catch (err) {
- if (!stage.isConnected) return
+ if (!stage.isConnected || myRequest !== statsRequestId) return
data.innerHTML = `${esc(err.status === 503 ? 'Auswertung gerade nicht erreichbar' : err.message)}
`
return
}
- if (!stage.isConnected) return
+ if (!stage.isConnected || myRequest !== statsRequestId) return
const t = stats.totals || {}
const rate = Math.round((Number(t.consentRate) || 0) * 100)
const kpi = (label, value) => `${label}${esc(value)}
`
@@ -725,15 +731,19 @@ function renderAnalyseHeat(box, projectId, pages) {
- `
+
+ `
const go = box.querySelector('#an-heat-go')
const error = box.querySelector('#an-heat-error')
go.addEventListener('click', async () => {
go.disabled = true
error.classList.add('hidden')
+ box.querySelector('#an-heat-link').innerHTML = ''
try {
const { url } = await request('/api/funktionen/analyse/heatmap-link', { method: 'POST', body: { projectId, path: box.querySelector('#an-heat-page').value || '/', vp: box.querySelector('#an-heat-vp').value } })
- if (safeUrl(url)) window.open(url, '_blank', 'noopener')
+ // Kein window.open nach await (Safari/iOS blockiert das): sichtbarer Link, den der Kunde selbst antippt
+ const link = box.querySelector('#an-heat-link')
+ link.innerHTML = safeUrl(url) ? `Heatmap öffnen` : ''
} catch (err) {
error.textContent = err.status === 503 ? 'Auswertung gerade nicht erreichbar' : err.message
error.classList.remove('hidden')
diff --git a/server/services/analyse.js b/server/services/analyse.js
index 9b22f33..6f6a675 100644
--- a/server/services/analyse.js
+++ b/server/services/analyse.js
@@ -139,6 +139,7 @@ export async function activateAnalyse({ customerId, projectId, accentColor, cons
const eligibility = await analyseEligibility(customerId, projectId)
if (!eligibility.ok) throw httpError(ELIGIBILITY_STATUS[eligibility.reason], ELIGIBILITY_MESSAGES[eligibility.reason])
const { project } = eligibility
+ if (!analyseDomains(project).length) throw httpError(409, 'Keine gültige Domain für die Analyse')
const existing = await ensureFeatureDoc(customerId, projectId, FEATURE_KEY)
if (existing.state === 'disabled' && by === 'customer') {
diff --git a/test/analyseRoutes.test.js b/test/analyseRoutes.test.js
index 5b7ca2d..e4e52c9 100644
--- a/test/analyseRoutes.test.js
+++ b/test/analyseRoutes.test.js
@@ -230,10 +230,11 @@ test('Härtung: ungültige Hosts, Pfadzeichen, Fehler bei einem Projekt', async
const link = (path) => post(`${A}/heatmap-link`, { projectId: 'p1', path, vp: 'd' }, { cookie })
for (const path of ['/a\\b', '/a b', '/a\nb', '/a\u0000b', '/a\tb', '/a\u007fb']) assert.equal((await link(path)).status, 400, JSON.stringify(path))
assert.equal((await link('/über-uns')).status, 200)
- // keine gueltige Domain => 409
+ // keine gueltige Domain => Aktivieren scheitert mit 409, vor dem Sammeldienst
aw.seed('websiteProjects', { $id: 'p7', customerId: 'c1', projectName: 'Kaputt', subdomain: 'ka putt', liveDomain: 'x_y', isPublic: true })
- await post(`${A}/activate`, { projectId: 'p7', consent: consent() }, { cookie })
- const bad = await post(`${A}/heatmap-link`, { projectId: 'p7', path: '/', vp: 'd' }, { cookie })
+ const before = puts.length
+ const bad = await post(`${A}/activate`, { projectId: 'p7', consent: consent() }, { cookie })
assert.equal(bad.status, 409)
- assert.equal(bad.body.error, 'Keine gültige Domain')
+ assert.equal(bad.body.error, 'Keine gültige Domain für die Analyse')
+ assert.equal(puts.length, before)
})