Files
Webklar-Kundenbereich/docs/superpowers/specs/2026-09-30-funktionen-news-design.md

14 KiB
Raw Permalink Blame History

Funktionen-Tab und News-Pinnwand – Design

Stand: 2026-09-30 · Status: vom Nutzer im Chat freigegeben (Abschnitte 1–6), Spec zur Durchsicht Teilprojekte: A Portal-Umbau + B News (diese Spec). C Analyse mit Cookie-Banner ist ausdrücklich nicht Teil dieser Spec und bekommt eine eigene.

Ziel

Kunden können im Kundenportal Zusatzfunktionen für ihre Website buchen. Die erste ist News: der Kunde schreibt im Portal kurze Beiträge (Text, Foto oder beides), die auf seiner Website als Pinnwand erscheinen – jeder Besucher sieht sie. Referenz für Optik und Bedienung ist der freigegebene Entwurf „Schatzkiste Pinnwand-Entwurf“ (https://claude.ai/artifact/E1ad9iK7P91Su2GrLKJQMm, Quelle /root/reports/emmas-news-entwurf/pinnwand-entwurf.html, Kork-Hintergrund mit Holzrahmen #A8733F).

Entscheidungen (vom Nutzer)

# Entscheidung
E1 Tab „Fortschritt“ entfällt, sein Inhalt wandert unter „Projekte“.
E2 Neuer Tab heißt Funktionen. Später kommen weitere Funktionen dazu (z. B. Terminplaner).
E3 News ist standardmäßig aus. Jeder Kunde kann es buchen, ein Abo ist nicht nötig.
E4 Preis: 30 € einmalig für den Einbau, die Nutzung danach ist kostenlos. Kein Abo, kein Testmonat.
E5 Mit der Buchung wird ein Ticket eröffnet; erst wenn es geschlossen ist, ist News für den Kunden aktiv. Es gibt keine feste Fertigstellungszeit, nur eine grobe Einschätzung.
E6 Anzeigedauer pro Beitrag: 1 Woche · 2 Wochen · 1 Monat (Vorauswahl) · 3 Monate · bis ich ihn abnehme · bis zu einem Datum. Abgelaufenes bleibt im Portal unter „Abgelaufen“ und lässt sich wieder aufhängen.
E7 Website zeigt die neuesten 6 Beiträge, ältere über „Ältere Neuigkeiten ansehen“.
E8 Auslieferung über Weg 1: öffentliche Lese-Schnittstelle im Portal + Skript auf der Kundenwebsite.
E9 Admin kann News ohne Zahlung aktivieren (Pilot Emma's Schatzkiste, Kulanz).

A. Portal-Umbau

Navigation (Seitenleiste am Desktop, Tab-Leiste unten in der App-Ansicht ≤ 880 px): Projekte · Funktionen · Rechnungen · Chat · Verträge.

Projekte (#view-projects): unter den Projektkarten eine Sektion „Was wir zuletzt erledigt haben“ mit dem bisherigen Inhalt von loadCustomerActivity (Karte je Auftrag mit Status, Fortschrittsleiste, Arbeiten). Standardmäßig die 3 neuesten Aufträge, darunter „Alle anzeigen“. #view-activity und der Nav-Eintrag entfallen. VIEW_HASHES/Router: der Hash #fortschritt öffnet Projekte und scrollt zur Sektion (alte Links aus Mails und Lesezeichen). APP_PARENT_LABEL.activity entfällt.

Funktionen (#view-funktionen, Hash #funktionen): Kachel-Übersicht, eine Kachel je Funktion.

  • News: Status-Pille Aus / In Einrichtung / Aktiv und der passende Knopf („Einrichten lassen · 30 €“ / „Status ansehen“ / „Beiträge schreiben“).
  • Terminplaner, Analyse: Pille „Kommt bald“, ohne Aktion.
  • News öffnet #view-news (Hash #news); in der App-Ansicht Unterseite mit „Zurück zu Funktionen“ (APP_PARENT.news = 'funktionen'), am Desktop normale Ansicht.
  • Hat der Kunde mehrere Website-Projekte, gilt News pro Projekt; die News-Ansicht hat dann oben eine Projektauswahl, die Kachel zeigt „aktiv für 1 von 2 Websites“.

B1. Buchen und Freischalten

Zustände je (Kunde, Projekt) im vorhandenen Collection portalFeatures (featureKey = 'news', projectId, enabled, unlockedByPurchase, purchaseStatus). Ergänzt werden Attribute: state (off | ordered | setup | active | disabled), ticketId, stripeSessionId, stripeInvoiceId, orderedAt, activatedAt, siteKey. enabled bleibt als abgeleitetes Feld (state === 'active') für Rückwärtskompatibilität von GET /api/features.

off ──Checkout bezahlt──▶ setup ──Ticket geschlossen──▶ active
 │                          ▲                              │
 └──Admin „aktivieren“──────┴──────────────────────────────┘ (Admin kann direkt active setzen)
active ──Admin „abschalten“ / Kunde verlässt WEBklar──▶ disabled

ordered ist der kurze Zustand zwischen Checkout-Start und Zahlungseingang (Checkout verfallen ⇒ zurück auf off).

Buchungsseite (in #view-news, solange off):

  • Was der Kunde bekommt: Pinnwand-Sektion im Stil seiner Website, Einbau durch uns, Beiträge danach jederzeit selbst schreiben, dauerhaft ohne weitere Kosten.
  • Grobe Zeiteinschätzung: „meist innerhalb von 1–2 Wochen“ (Text in config, kein Versprechen).
  • Projektauswahl bei mehreren Websites.
  • Pflicht-Checkbox (Widerruf, analog Abo-Bestellung): „Ich verlange ausdrücklich, dass WEBklar vor Ablauf der Widerrufsfrist mit dem Einbau beginnt. Mir ist bekannt, dass mein Widerrufsrecht mit der vollständigen Erbringung des Einbaus erlischt (§ 356 Abs. 5 Nr. 2 BGB). Widerrufe ich vor der vollständigen Erbringung, schulde ich einen angemessenen Betrag für die bis dahin erbrachte Leistung (§ 357a Abs. 2 BGB).“ Einwilligung wird mit Zeitstempel und Textfassung am Feature-Dokument gespeichert (wie die Abo-Consent-Metadaten). Korrekturvermerk 2026-10-05 (Terminplaner TP2, Task F2): Ursprünglich stand hier „… erlischt, sobald der Einbau vollständig erbracht ist (§ 356 Abs. 4 BGB).“ Zitat und Wertersatz-Satz sind korrigiert bzw. ergänzt (Entwurf bis zum Befund der Kanzlei, Kanzlei-Paket Frage Q3). Bestehende Bestellungen behalten den damals gespeicherten Text und die damalige Fassung.
  • Knopf „Zahlungspflichtig bestellen · 30 €“ ⇒ POST /api/funktionen/news/checkout.

Zahlung: Stripe Checkout mode: 'payment', einmaliger Preis über ensurePrice('news_setup') (Katalog in config/services/products.js, 30,00 €; Korrekturvermerk 2026-10-05: ohne Umsatzsteuer gemäß § 19 UStG, nicht „brutto“), invoice_creation.enabled = true, Zahlarten wie beim Abo (Karte, SEPA). Metadaten { customerId, projectId, featureKey: 'news' }.

Stripe-Webhook checkout.session.completed (bzw. checkout.session.async_payment_succeeded bei SEPA):

  1. Feature-Dokument auf setup, stripeInvoiceId merken.
  2. WOMS-Ticket anlegen (neue Funktion createFeatureTicket in ticketAdmin.js, gleiche Felder wie ensureProjectTicket): topic „News-Pinnwand einbauen – “, type 'Webpage', status 'Open', details mit Projekt, Website-Domain, siteKey und Link zur Spec/Einbauanleitung. ticketId am Feature-Dokument.
  3. Mails: Kunde „Deine Bestellung: News-Pinnwand“ (neue Vorlage im Stil von K01), Team-Hinweis (T-Vorlage). Idempotent über stripeSessionId (Webhook kann mehrfach kommen).

Aktivieren: Hintergrund-Job alle 5 Minuten (neben den vorhandenen setInterval-Jobs in server/index.js) plus sofortige Prüfung beim Öffnen von Funktionen: Features in setup, deren Ticket status === 'Closed' hat ⇒ active, activatedAt, Mail an Kunden „Deine Pinnwand ist bereit“. Wird ein Ticket wieder geöffnet, bleibt die Funktion aktiv (kein Zurückschalten).

Rechnung: kommt per Stripe-Mail; zusätzlich Link „Rechnung ansehen“ in der News-Ansicht (hosted_invoice_url). Der Tab „Rechnungen“ (Invoice Ninja) bleibt unverändert.

Admin (admin.html, Kundenansicht): je Projekt Zeile „News“ mit Zustand und Knöpfen „Aktivieren (ohne Zahlung)“ / „Abschalten“. Protokolliert wer/wann.

B2. Editor im Portal (#view-news, Zustand active)

Aufbau wie der Entwurf („Meine Pinnwand“):

  • Schritt 1: Art – Text · Foto · Foto + Text.
  • Schritt 2: Foto (Datei; am Handy zusätzlich „Foto aufnehmen“ per capture), Überschrift (≤ 60 Zeichen, bei Foto „Bildunterschrift“), Text (≤ 400 Zeichen, Zähler), Zettelfarbe (Sonnengelb, Himmelblau, Koralle, Weiß), Anzeigedauer (E6; „bis zu einem Datum“ öffnet Datumsfeld, frühestens morgen, höchstens 1 Jahr).
  • Live-Vorschau „so sehen es alle“ (Desktop rechts, sticky; Handy aufklappbar).
  • Knopf „Aufhängen“; beim Ändern „Speichern“.
  • Liste „Hängt gerade“ (Ändern, Abnehmen) und „Abgelaufen“ (Wieder aufhängen = neue Dauer wählen, Löschen mit Rückfrage in der Seite, kein confirm()).
  • Oben „Auf meiner Website ansehen“ (Link auf die Live-Domain des Projekts).
  • Zustand setup: Statuskarte „Wir bauen deine Pinnwand ein“ mit Ticketnummer, Bestelldatum, Zeiteinschätzung, Rechnungslink, „Frage dazu? Chat öffnen“.

API (alle requireSession, Kunde muss Eigentümer des Projekts sein, Feature active):

  • GET /api/funktionen – Zustände aller Funktionen je Projekt.
  • POST /api/funktionen/news/checkout – { projectId, consent } ⇒ { url }.
  • GET /api/news?projectId= – eigene Beiträge (hängend + abgelaufen).
  • POST /api/news – neu; PATCH /api/news/:id – ändern/abnehmen/wieder aufhängen; DELETE /api/news/:id.
  • POST /api/news/image – Upload (multipart), Antwort { imageId }. Rate-Limit: höchstens 30 Schreibvorgänge und 20 Uploads pro Kunde und Stunde.

B3. Speicherung

Collection newsPosts (Schema-Skript wie portal-setup.mjs): customerId, projectId, type (note|photo|combo), title (60), text (400), imageId, paper (sun|sky|coral|white), visibleFrom (ISO), visibleUntil (ISO oder leer = bis abgenommen), takenDownAt (ISO oder leer), createdAt, updatedAt. Index auf (projectId, visibleFrom). „Hängt“ ⇔ takenDownAt leer ∧ visibleFrom ≤ jetzt ∧ (visibleUntil leer ∨ visibleUntil > jetzt).

Bilder: upload-data/news/<projectId>/<uuid>.webp (Muster wie chatAttachments.js). Annahme: JPEG, PNG, WebP, HEIC ≤ 10 MB. Verarbeitung mit sharp (neue Abhängigkeit): EXIF-Rotation anwenden, alle Metadaten entfernen (GPS!), längste Kante ≤ 1600 px, WebP q≈80; zusätzlich eine Vorschau ≤ 480 px. Nicht verwendete Uploads werden nach 24 h gelöscht (wie Chat). Beim Löschen eines Beitrags wird das Bild mitgelöscht.

Aufbewahrung: Beiträge bleiben, bis der Kunde sie löscht. Bei disabled werden sie nicht mehr ausgeliefert; die Löschung folgt der bestehenden Kundendaten-Frist in services/retention.js (Erweiterung um newsPosts + Bilder).

B4. Anzeige auf der Kundenwebsite

siteKey: zufälliger, nicht erratbarer Schlüssel je Feature-Dokument (z. B. 16 Zeichen base32), damit keine Projekt- oder Kunden-IDs öffentlich werden.

Öffentliche Schnittstelle (ohne Login, nur GET):

  • GET /api/public/news/:siteKey?page=1 ⇒ { posts: [{ id, type, title, text, paper, date, image: { src, w, h } }], hasMore }, 6 pro Seite, neueste zuerst. Nur hängende Beiträge; Feature nicht active ⇒ { posts: [], hasMore: false }. Keine internen Felder. Cache-Control: public, max-age=60, CORS * (nur GET), ETag.
  • GET /api/public/news/:siteKey/img/:imageId(.webp|-s.webp) ⇒ Bild, nur wenn der Beitrag hängt; Cache-Control: public, max-age=86400, X-Content-Type-Options: nosniff.
  • Rate-Limit pro IP großzügig (z. B. 120/min), damit nichts missbraucht wird.

Einbau-Skript https://project.webklar.com/embed/news.js (statisch aus public/embed/):

  • Sucht <div data-webklar-news="<siteKey>">, lädt die Beiträge, rendert Zettel (Text-Zettel, Polaroid, Polaroid + Zettel) wie im Entwurf inkl. „Neu“-Schild am neuesten, Leerzustand „Hier hängt bald was Neues.“, „Ältere Neuigkeiten ansehen“ (lädt Seite 2 nach), Handy: Wisch-Reihe.
  • Optik über CSS-Variablen, die die Seite setzt (--wkn-board, --wkn-frame, --wkn-font-display, --wkn-font-body, Zettelfarben); Standard = Kork + Holzrahmen.
  • Kein Cookie, kein Storage, keine weiteren Drittanfragen. Text wird als Text eingesetzt (kein innerHTML mit Kundentext). Ohne JS oder bei Fehler bleibt der statische Leerzustand im HTML stehen.
  • Der Einbau (die 30 €) = Sektion im Stil der Kundenseite + div + Skript + Datenschutz-Absatz.

Datenschutz:

  • Kundenwebsite: Absatz „Neuigkeiten“ (Abruf von project.webklar.com; bei bei uns gehosteten Seiten ist WEBklar ohnehin Hoster/Auftragsverarbeiter; keine Cookies).
  • Portal-Datenschutzerklärung: Abschnitt zu News-Beiträgen und Fotos (Zweck, Speicherort, Löschung, Entfernen der Metadaten, öffentliche Sichtbarkeit auf der eigenen Website).
  • Beide Texte werden als Entwurf formuliert und dem Nutzer zur Freigabe vorgelegt.

Pilot: Emma's Schatzkiste – Admin aktiviert News ohne Zahlung, Einbau in Kork-Optik auf der Startseite (Position nach dem Hero, Nav-Punkt „Neuigkeiten“).

Fehlerfälle

  • Checkout abgebrochen/verfallen ⇒ off, Kunde kann neu buchen.
  • Webhook doppelt ⇒ idempotent über stripeSessionId.
  • SEPA-Zahlung scheitert nachträglich ⇒ off, Ticket bekommt Notiz, Team-Mail.
  • Ticket gelöscht statt geschlossen ⇒ Feature bleibt setup, Team-Hinweis im Admin („Ticket fehlt“).
  • Upload zu groß / falscher Typ ⇒ Meldung am Feld („Das Foto ist größer als 10 MB. Bitte ein kleineres wählen.“).
  • Appwrite nicht erreichbar ⇒ Portal zeigt Fehler am Editor; öffentliche Schnittstelle liefert 503, das Skript lässt den Leerzustand stehen.

Tests

  • npm test (node --test), mit den vorhandenen Test-Doubles (test/support/ appwriteMock, fakeStripe): Zustandsübergänge inkl. Idempotenz, Ticket geschlossen ⇒ aktiv, Ablauf-Logik „hängt“ an Grenzen (Zeitzonen Europe/Berlin), öffentliche Schnittstelle gibt nur Freigegebenes und keine internen Felder aus, Bildverarbeitung entfernt EXIF/GPS, Rechte (fremdes Projekt ⇒ 403), Rate-Limits.
  • Playwright auf Fixture-Server: Projekte mit Fortschritt-Sektion, #fortschritt-Umleitung, Funktionen- Kacheln in allen drei Zuständen, Buchungsseite, Editor (PC + Handy), news.js auf einer Testseite (leer, 1 Beitrag, 8 Beiträge mit „Ältere“), ohne JS.
  • Stripe-Checkout gegen fakeStripe, wie bei den Abo-Tests.

Nicht Teil dieser Spec

  • Teilprojekt C „Analyse“ (Cookie-Banner, Einwilligung, Auswertung).
  • Terminplaner.
  • Mehrsprachigkeit, Kommentare/Likes unter Beiträgen, geplante Veröffentlichung in der Zukunft.

Offene Punkte zur Freigabe

  1. Texte: Buchungsseite, Widerrufs-Checkbox, Kunden-/Team-Mails, Datenschutz-Absätze (Portal + Website) – Entwurf kommt mit dem Plan.
  2. Braucht es einen Zusatz in den AGB/Vertragsunterlagen für die Einrichtungsleistung „News“, oder reicht die Leistungsbeschreibung auf der Buchungsseite? (Empfehlung: kurze Leistungsbeschreibung als eigenes Dokument „Funktionen – Leistungsbeschreibung“, auf der Buchungsseite verlinkt.)