Funktionen-Seite: Preis und Bestelltexte aus dem Register

GET /api/funktionen liefert features.news und features.termine (oeffentlicheAnsicht,
auch wenn der Terminplaner noch nicht freigegeben ist) und ustHinweis; setupEstimate und
consentText bleiben als News-Alias. Die News-Kachel und die Buchungsseite lesen Preis,
Knopftext und Zustimmungstext aus features.news und fallen ohne features auf die bisherigen
Werte zurueck. News sendet weiter consent: true an /api/funktionen/news/checkout.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-04 13:42:20 +00:00
1 parent f57dad73ca
commit cf9f9b30a0
4 files changed
+186 -13

No files matched your search

+25 -11
View File
@@ -6,7 +6,7 @@
import { bannerColor, BANNER_PREVIEW_TEXT, controllerLines, chartModel, barRows, countryName, flagEmoji, refLabel, DEVICE_LABELS, CONTACT_LABELS } from '/analyse-view.js'
import { HEAT_DEVICES, deviceHtml, heatCaption } from '/geraete.js'
const PRICE = '30 €'
const PRICE = '30 €' // Rueckfall, wenn die Antwort von /api/funktionen kein features.news (Register) enthaelt
const LEISTUNGSBESCHREIBUNG = '/vertragsunterlagen/funktionen-leistungsbeschreibung.html'
const PHOTO_ACCEPT = 'image/jpeg,image/png,image/webp' // iOS wandelt HEIC dann selbst in JPEG um
const PHOTO_MAX_BYTES = 10 * 1024 * 1024
@@ -63,6 +63,20 @@ async function request(path, { method = 'GET', body, raw } = {}) {
const selection = { projectId: '' }
const newsOf = (project) => project.features?.news || { state: 'off' }
// Preis, Knopf und Zustimmungstext der News-Buchung aus dem Register (features.news der Antwort von /api/funktionen);
// fehlt das Register oder ein Wert, gelten die bisherigen Werte (PRICE und der consentText der Antwort)
const textOr = (value, fallback) => (typeof value === 'string' && value.trim() ? value : fallback)
function newsOffer(features, consentFallback = '') {
const reg = features?.news
const price = textOr(reg?.priceLabel, PRICE)
return {
price,
knopf: textOr(reg?.consent?.knopf, `Zahlungspflichtig bestellen · ${price}`),
consentText: textOr(reg?.consent?.items?.[0]?.text, consentFallback),
}
}
const isSetup = (state) => state === 'ordered' || state === 'setup'
// Vorauswahl: zuerst ein aktives Projekt, dann eins in Einrichtung, sonst das erste
@@ -78,11 +92,11 @@ const ICONS = {
}
const svg = (name) => `<svg class="fn-ico" viewBox="0 0 24 24" aria-hidden="true">${ICONS[name]}</svg>`
function newsTile(projects) {
function newsTile(projects, price = PRICE) {
const states = projects.map((p) => newsOf(p).state)
const active = states.filter((s) => s === 'active').length
let pill = ['Aus', 'off']
let cta = `Einrichten lassen · ${PRICE}`
let cta = `Einrichten lassen · ${esc(price)}`
if (active) { pill = ['Aktiv', 'on']; cta = 'Beiträge schreiben' } else if (states.some(isSetup)) { pill = ['In Einrichtung', 'setup']; cta = 'Status ansehen' } else if (states.length && states.every((s) => s === 'disabled')) { pill = ['Abgeschaltet', 'off']; cta = 'Status ansehen' }
const target = pickProject(projects)
const count = projects.length > 1 && active ? `<p class="fn-count">aktiv für ${active} von ${projects.length} Websites</p>` : ''
@@ -199,10 +213,10 @@ const soonTile = (icon, title, text) => `
export async function initFunktionen(root) {
if (!root) return
const { projects = [], termine } = await request('/api/funktionen')
const { projects = [], termine, features } = await request('/api/funktionen')
root.innerHTML = `
<div class="fn-tiles">
${newsTile(projects)}
${newsTile(projects, newsOffer(features).price)}
${termine?.verfuegbar ? termineTile(projects) : soonTile('calendar', 'Terminplaner', TERMINE_TEXT)}
${analyseTile(projects)}
</div>`
@@ -220,7 +234,7 @@ export async function initFunktionen(root) {
export async function initNews(root, { projectId } = {}) {
if (!root) return
if (projectId) selection.projectId = projectId
const { projects = [], setupEstimate = '', consentText = '' } = await request('/api/funktionen')
const { projects = [], setupEstimate = '', consentText = '', features } = await request('/api/funktionen')
if (!projects.length) {
root.innerHTML = '<div class="kb-panel kb-empty">Noch keine Website-Projekte zugewiesen.</div>'
return
@@ -255,10 +269,10 @@ export async function initNews(root, { projectId } = {}) {
if (feature.state === 'active') renderEditor(stage, project)
else if (isSetup(feature.state)) stage.innerHTML = thanks + statusCard(feature, setupEstimate)
else if (feature.state === 'disabled') stage.innerHTML = '<div class="kb-panel"><h3>News-Pinnwand ist abgeschaltet</h3><p>Für diese Website ist die Pinnwand im Moment nicht aktiv. <button type="button" class="kb-link" data-view="chat">Schreib uns im Chat</button>, dann klären wir das.</p></div>'
else renderBooking(stage, project, { setupEstimate, consentText, thanks })
else renderBooking(stage, project, { setupEstimate, offer: newsOffer(features, consentText), thanks })
}
function renderBooking(stage, project, { setupEstimate, consentText, thanks }) {
function renderBooking(stage, project, { setupEstimate, offer, thanks }) {
stage.innerHTML = `
${thanks}
<div class="kb-panel fn-booking">
@@ -269,14 +283,14 @@ function renderBooking(stage, project, { setupEstimate, consentText, thanks }) {
<li>Beiträge jederzeit selbst schreiben, ändern und abnehmen</li>
<li>Nutzung danach kostenlos, kein Abo</li>
</ul>
<p class="fn-price"><strong>${PRICE}</strong> <span>einmalig, inklusive Mehrwertsteuer</span></p>
<p class="fn-price"><strong>${esc(offer.price)}</strong> <span>einmalig, inklusive Mehrwertsteuer</span></p>
${setupEstimate ? `<p class="fn-estimate">Einrichtung: ${esc(setupEstimate)}</p>` : ''}
<p class="fn-desc-link"><a href="${LEISTUNGSBESCHREIBUNG}" target="_blank" rel="noopener">Leistungsbeschreibung</a></p>
<label class="fn-consent"><input type="checkbox" id="news-consent"><span id="news-consent-text"></span></label>
<p class="error hidden" id="news-order-error" role="alert"></p>
<div class="abo-actions"><button type="button" class="abo-btn" id="news-order" disabled>Zahlungspflichtig bestellen · ${PRICE}</button></div>
<div class="abo-actions"><button type="button" class="abo-btn" id="news-order" disabled>${esc(offer.knopf)}</button></div>
</div>`
stage.querySelector('#news-consent-text').textContent = consentText
stage.querySelector('#news-consent-text').textContent = offer.consentText
const box = stage.querySelector('#news-consent')
const button = stage.querySelector('#news-order')
const error = stage.querySelector('#news-order-error')