14 KiB
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):
- Feature-Dokument auf
setup,stripeInvoiceIdmerken. - WOMS-Ticket anlegen (neue Funktion
createFeatureTicketinticketAdmin.js, gleiche Felder wieensureProjectTicket):topic„News-Pinnwand einbauen – “,type'Webpage',status'Open',detailsmit Projekt, Website-Domain, siteKey und Link zur Spec/Einbauanleitung.ticketIdam Feature-Dokument. - 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 nichtactive⇒{ 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.jsauf 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
- Texte: Buchungsseite, Widerrufs-Checkbox, Kunden-/Team-Mails, Datenschutz-Absätze (Portal + Website) – Entwurf kommt mit dem Plan.
- 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.)