diff --git a/CHANGELOG.md b/CHANGELOG.md index d6c173b..52316a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ Versionsnummer in der Fußzeile. > Diese Datei wird erzeugt: `npm run changelog`. > Gepflegt wird der Verlauf in `src/lib/changelog.ts`. +## 1.7.1 — 21.08.2026 + +Bilder zu den neuen Funktionen in der Bedienungsanleitung. + +- Die Bedienungsanleitung zeigt die Funktionen aus 1.7.0 jetzt auch im Bild: Logo im Code, Dublettenwarnung, Filter nach Art, Freigabe je Gruppe, Kurzhilfe und Domain-Bericht. +- Alle übrigen Bilder mit erneuert — sie stammten noch aus 1.2.0. +- Das Skript, das die Bilder erzeugt, lief seit einer Umbenennung ins Leere und tut es wieder. + ## 1.7.0 — 21.08.2026 Logo im Code, Freigabe je Gruppe und ein Domain-Check. diff --git a/docs/anleitung.md b/docs/anleitung.md index e6f8319..7b88aba 100644 --- a/docs/anleitung.md +++ b/docs/anleitung.md @@ -114,6 +114,8 @@ trennen kann. ### Ein Logo in die Mitte setzen +![Ein Logo in der Mitte des Codes](bilder/16-logo-im-code.png) + Ganz unten in der Gestaltung steht **Logo in der Mitte**. Ein Haken genügt, dann fragt die Anwendung, woher das Bild kommen soll: @@ -158,6 +160,7 @@ Projekte nebeneinander. liegen. - Ein **Filter nach Art** erscheint, sobald dynamische und statische Codes nebeneinander liegen: *Alle Arten*, *Nur dynamische*, *Nur statische*. + ![Die Liste, eingeschränkt auf statische Codes](bilder/18-meine-codes-filter.png) Steht er auf „Nur statische“, fällt die Scan-Spalte weg und die Sortierung nach Scans mit ihr — bei einer Liste aus lauter Strichen sagt sie nichts. - Das Symbol neben der Kurz-URL kopiert sie in die Zwischenablage. @@ -173,6 +176,8 @@ Ein statischer Code ist mit dem Download eigentlich fertig — sein Inhalt steckt in ihm. Der Knopf **In „Meine Codes“ ablegen** legt ihn trotzdem in die Liste, damit du ihn wiederfindest und erneut herunterladen kannst. +![Warnung vor einem doppelt abgelegten Code](bilder/17-dublettenwarnung.png) + Liegt dort schon ein statischer Code mit **identischem Inhalt**, hält das Ablegen an und zeigt, welcher das ist und wann er entstand. Der Knopf heißt dann **Trotzdem ablegen** — es ist eine Warnung, keine Sperre. Verglichen wird @@ -296,6 +301,8 @@ bearbeiten. Nur neue lassen sich nicht mehr anlegen. **Freigabe je Gruppe.** Über der Liste steht die Auswahl **Freigabe für**: +![Freigabe der Typen für eine einzelne Gruppe](bilder/19-verwaltung-typen-gruppe.png) + | Auswahl | Wirkung | | --- | --- | | Alle Gruppen (Grundeinstellung) | gilt für die ganze Installation | @@ -346,9 +353,13 @@ Knopf **Kurzhilfe** über der Liste führt es in sechs Schritten auf — DNS, Weiterleitung im Proxy samt der drei nötigen Kopfzeilen, Zertifikat, Registrieren, Gruppenzuteilung, Nachprüfen. +![Die Kurzhilfe zum Hinzufügen einer Domain](bilder/20-domain-kurzhilfe.png) + **Prüfen, ob eine Domain wirklich hierher führt.** Bei jedem Eintrag steht ein Knopf **Prüfen**. Er geht den Weg der Scanner nach und meldet vier Dinge: +![Der Bericht einer geprüften Domain](bilder/21-domain-check.png) + | Schritt | Was er sagt | | --- | --- | | DNS | ob der Name auflöst, und auf welche Adressen | diff --git a/docs/bilder/01-startseite.png b/docs/bilder/01-startseite.png index 161c39a..b45c019 100644 Binary files a/docs/bilder/01-startseite.png and b/docs/bilder/01-startseite.png differ diff --git a/docs/bilder/02-website-formular.png b/docs/bilder/02-website-formular.png index 2967911..92833cd 100644 Binary files a/docs/bilder/02-website-formular.png and b/docs/bilder/02-website-formular.png differ diff --git a/docs/bilder/03-dynamisch.png b/docs/bilder/03-dynamisch.png index 44469f4..ef060b3 100644 Binary files a/docs/bilder/03-dynamisch.png and b/docs/bilder/03-dynamisch.png differ diff --git a/docs/bilder/04-meine-codes-liste.png b/docs/bilder/04-meine-codes-liste.png index e02ede6..e269d7f 100644 Binary files a/docs/bilder/04-meine-codes-liste.png and b/docs/bilder/04-meine-codes-liste.png differ diff --git a/docs/bilder/05-meine-codes-karten.png b/docs/bilder/05-meine-codes-karten.png index 3bdc1d2..146fa0a 100644 Binary files a/docs/bilder/05-meine-codes-karten.png and b/docs/bilder/05-meine-codes-karten.png differ diff --git a/docs/bilder/06-ziel-bearbeiten.png b/docs/bilder/06-ziel-bearbeiten.png index 0392a19..21a9f39 100644 Binary files a/docs/bilder/06-ziel-bearbeiten.png and b/docs/bilder/06-ziel-bearbeiten.png differ diff --git a/docs/bilder/07-landing-links.png b/docs/bilder/07-landing-links.png index e3c1ceb..85f8dc2 100644 Binary files a/docs/bilder/07-landing-links.png and b/docs/bilder/07-landing-links.png differ diff --git a/docs/bilder/08-landing-social.png b/docs/bilder/08-landing-social.png index 568ad60..2746553 100644 Binary files a/docs/bilder/08-landing-social.png and b/docs/bilder/08-landing-social.png differ diff --git a/docs/bilder/09-landing-pdf.png b/docs/bilder/09-landing-pdf.png index 51e3fe7..ba19196 100644 Binary files a/docs/bilder/09-landing-pdf.png and b/docs/bilder/09-landing-pdf.png differ diff --git a/docs/bilder/09b-landing-feedback.png b/docs/bilder/09b-landing-feedback.png index 8b819a4..7aa8078 100644 Binary files a/docs/bilder/09b-landing-feedback.png and b/docs/bilder/09b-landing-feedback.png differ diff --git a/docs/bilder/10-feedback-auswertung.png b/docs/bilder/10-feedback-auswertung.png index 36b1b66..2c3e1cc 100644 Binary files a/docs/bilder/10-feedback-auswertung.png and b/docs/bilder/10-feedback-auswertung.png differ diff --git a/docs/bilder/11-verwaltung.png b/docs/bilder/11-verwaltung.png index 5f8d58b..76962ab 100644 Binary files a/docs/bilder/11-verwaltung.png and b/docs/bilder/11-verwaltung.png differ diff --git a/docs/bilder/12-verwaltung-typen.png b/docs/bilder/12-verwaltung-typen.png index f413836..fa424cf 100644 Binary files a/docs/bilder/12-verwaltung-typen.png and b/docs/bilder/12-verwaltung-typen.png differ diff --git a/docs/bilder/13-verwaltung-gruppen.png b/docs/bilder/13-verwaltung-gruppen.png index 87c1291..4cf760f 100644 Binary files a/docs/bilder/13-verwaltung-gruppen.png and b/docs/bilder/13-verwaltung-gruppen.png differ diff --git a/docs/bilder/14-verwaltung-domains.png b/docs/bilder/14-verwaltung-domains.png index 434dbbb..1906c23 100644 Binary files a/docs/bilder/14-verwaltung-domains.png and b/docs/bilder/14-verwaltung-domains.png differ diff --git a/docs/bilder/15-konto.png b/docs/bilder/15-konto.png index 4f4b91f..972f459 100644 Binary files a/docs/bilder/15-konto.png and b/docs/bilder/15-konto.png differ diff --git a/docs/bilder/16-logo-im-code.png b/docs/bilder/16-logo-im-code.png new file mode 100644 index 0000000..4be0f11 Binary files /dev/null and b/docs/bilder/16-logo-im-code.png differ diff --git a/docs/bilder/17-dublettenwarnung.png b/docs/bilder/17-dublettenwarnung.png new file mode 100644 index 0000000..3ce3277 Binary files /dev/null and b/docs/bilder/17-dublettenwarnung.png differ diff --git a/docs/bilder/18-meine-codes-filter.png b/docs/bilder/18-meine-codes-filter.png new file mode 100644 index 0000000..5527e6e Binary files /dev/null and b/docs/bilder/18-meine-codes-filter.png differ diff --git a/docs/bilder/19-verwaltung-typen-gruppe.png b/docs/bilder/19-verwaltung-typen-gruppe.png new file mode 100644 index 0000000..2647d71 Binary files /dev/null and b/docs/bilder/19-verwaltung-typen-gruppe.png differ diff --git a/docs/bilder/20-domain-kurzhilfe.png b/docs/bilder/20-domain-kurzhilfe.png new file mode 100644 index 0000000..80faf32 Binary files /dev/null and b/docs/bilder/20-domain-kurzhilfe.png differ diff --git a/docs/bilder/21-domain-check.png b/docs/bilder/21-domain-check.png new file mode 100644 index 0000000..5a5721d Binary files /dev/null and b/docs/bilder/21-domain-check.png differ diff --git a/docs/screenshots.mjs b/docs/screenshots.mjs index bfc7894..ed4b0cb 100644 --- a/docs/screenshots.mjs +++ b/docs/screenshots.mjs @@ -7,6 +7,9 @@ * Voraussetzungen: * - eine **leere** Instanz (die Beispieldaten würden sonst mit fremden * Codes im Bild landen), erreichbar unter BASE + * - `BASE_URL` der Instanz muss auf **dieselbe** Adresse zeigen wie BASE. + * Sonst scheitert der Domain-Check im Bild 21, weil die Anwendung sich + * selbst unter der eingetragenen Adresse nicht erreicht. * - AUTH_ENABLED=true, damit Kopfzeile und Verwaltung so aussehen wie im * Regelbetrieb * - Playwright: npm i -D playwright && npx playwright install chromium @@ -18,6 +21,7 @@ import { mkdir } from "node:fs/promises"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; import { chromium } from "playwright"; +import sharp from "sharp"; const BASE = (process.argv[2] ?? "http://localhost:3000").replace(/\/+$/, ""); const ZIEL = join(dirname(fileURLToPath(import.meta.url)), "bilder"); @@ -197,16 +201,47 @@ const handy = await browser.newContext({ locale: "de-DE", }); -async function schuss(context, pfad, datei, { ganzeSeite = true, vorher } = {}) { +/** + * Nimmt eine Seite auf. + * + * `bereich` grenzt auf einen Ausschnitt ein — für Dialoge und einzelne Karten, + * bei denen die ganze Seite mehr verdeckt als zeigt. + */ +async function schuss( + context, + pfad, + datei, + { ganzeSeite = true, vorher, bereich } = {}, +) { const page = await context.newPage(); await page.goto(BASE + pfad, { waitUntil: "networkidle" }); if (vorher) await vorher(page); await page.waitForTimeout(300); - await page.screenshot({ path: join(ZIEL, datei), fullPage: ganzeSeite }); + const ziel = join(ZIEL, datei); + if (bereich) { + await page.locator(bereich).first().screenshot({ path: ziel }); + } else { + await page.screenshot({ path: ziel, fullPage: ganzeSeite }); + } await page.close(); console.log(" ✓", datei); } +/** + * Ein Beispiellogo für die Aufnahmen zum Logo im Code. + * + * Erzeugt statt mitgeliefert: So liegt kein Bild im Repo, das niemand pflegt, + * und die Farbe passt zu den übrigen Aufnahmen. + */ +async function beispiellogo() { + const svg = ` + + + + `; + return sharp(Buffer.from(svg)).png().toBuffer(); +} + console.log("Bilder werden erzeugt:"); await schuss(desktop, "/", "01-startseite.png"); @@ -242,7 +277,7 @@ await schuss(desktop, "/meine-codes", "06-ziel-bearbeiten.png", { await page.waitForTimeout(400); // Bewusst die letzte Zeile: ein Website-Code, dessen Dialog ganz ins Bild passt. await page.locator("tbody tr").last().getByRole("button", { name: /bearbeiten/ }).click(); - await page.getByRole("heading", { name: "Ziel bearbeiten" }).waitFor(); + await page.getByRole("heading", { name: "Code bearbeiten" }).waitFor(); }, }); @@ -258,8 +293,129 @@ await schuss(desktop, "/verwaltung/gruppen", "13-verwaltung-gruppen.png"); await schuss(desktop, "/verwaltung/domains", "14-verwaltung-domains.png"); await schuss(desktop, "/konto", "15-konto.png"); +/* ------------------------------------------- Seit 1.7.0: die neuen Funktionen */ + +const logo = await beispiellogo(); + +// Logo in der Mitte: ganze Seite, damit Auswahl und Vorschau zusammen im Bild +// sind — das eine erklärt das andere. +await schuss(desktop, "/erstellen/website", "16-logo-im-code.png", { + vorher: async (page) => { + await page.locator("#website-url").fill("https://example.com/nordwind"); + await page.getByRole("checkbox", { name: /Logo in die Mitte/ }).check(); + await page.setInputFiles('input[type="file"]', { + name: "logo.png", + mimeType: "image/png", + buffer: logo, + }); + await page.waitForTimeout(2500); + }, +}); + +// Für die Dublettenwarnung braucht es einen bereits abgelegten statischen Code. +const statisch = await api("/api/codes", { + method: "POST", + json: { + type: "website", + data: { url: "https://example.com/aushang" }, + design: DESIGN, + mode: "static", + label: "Aushang Eingang", + }, +}); +if (statisch.status === 201) angelegt.push(statisch.body.code); + +await schuss(desktop, "/erstellen/website", "17-dublettenwarnung.png", { + bereich: "section:has-text('Art des Codes')", + vorher: async (page) => { + await page.locator("#website-url").fill("https://example.com/aushang"); + await page.locator('button[data-mode="static"]').click(); + await page.waitForTimeout(900); + await page.getByRole("button", { name: /ablegen/i }).click(); + await page.getByText("Diesen Inhalt hast du schon abgelegt").waitFor({ + timeout: 15000, + }); + }, +}); + +// Filter nach Art — sichtbar, weil jetzt beide Arten in der Liste stehen. +await schuss(desktop, "/meine-codes", "18-meine-codes-filter.png", { + vorher: async (page) => { + await page.getByLabel("Nach Art filtern").selectOption("static"); + await page.waitForTimeout(600); + }, +}); + +// Freigabe je Gruppe: eine zweite Gruppe, damit die Auswahl etwas zu wählen hat. +const gruppe = await api("/api/admin/groups", { + method: "POST", + json: { name: "Marketing" }, +}); +const gruppeId = gruppe.body?.group?.id ?? null; + +if (gruppeId) { + // Ein Typ in dieser Gruppe abgeschaltet, damit das Bild den Fall zeigt, + // um den es geht. + await api("/api/admin/types", { + method: "PATCH", + json: { type: "payment", enabled: false, groupId: gruppeId }, + }); +} + +await schuss(desktop, "/verwaltung/typen", "19-verwaltung-typen-gruppe.png", { + vorher: async (page) => { + if (gruppeId) { + await page.getByLabel("Freigabe für").selectOption(gruppeId); + await page.waitForTimeout(900); + } + }, +}); + +// Kurzhilfe: nur der Dialog, die Seite dahinter trägt nichts bei. +await schuss(desktop, "/verwaltung/domains", "20-domain-kurzhilfe.png", { + bereich: "dialog[open]", + vorher: async (page) => { + await page.getByRole("button", { name: "Kurzhilfe" }).click(); + await page.locator("dialog[open]").waitFor(); + await page.waitForTimeout(400); + }, +}); + +// Domain-Check: der Bericht zur eigenen Adresse. Weil BASE_URL und der Port +// des Containers hier zusammenpassen, geht die Prüfung durch — genau der +// Fall, den die Anleitung zeigen soll. +await schuss(desktop, "/verwaltung/domains", "21-domain-check.png", { + vorher: async (page) => { + await page.getByRole("button", { name: "Prüfen" }).first().click(); + await page + .locator("text=/Die Domain führt hierher|So kommt niemand hier an/") + .first() + .waitFor({ timeout: 30000 }); + await page.waitForTimeout(400); + }, +}); + await browser.close(); +/* --- Was über die Oberfläche entstand, kennt `angelegt` noch nicht --------- */ + +const bestand = await api("/api/codes"); +for (const eintrag of bestand.body?.codes ?? []) { + if (!angelegt.some((vorhanden) => vorhanden.id === eintrag.id)) { + angelegt.push(eintrag); + } +} + +// Die Abweichung und die Gruppe wieder zurücknehmen — das Skript soll eine +// Instanz so hinterlassen, wie es sie vorgefunden hat. +if (gruppeId) { + await api("/api/admin/types", { + method: "PATCH", + json: { type: "payment", enabled: true, groupId: gruppeId }, + }); + await api(`/api/admin/groups/${gruppeId}`, { method: "DELETE" }); +} + /* ------------------------------------------------------------ Aufräumen */ for (const eintrag of angelegt) { diff --git a/package.json b/package.json index 6950f1a..08a3b70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "qrcode-generator", - "version": "1.7.0", + "version": "1.7.1", "private": true, "scripts": { "dev": "next dev", diff --git a/src/lib/changelog.ts b/src/lib/changelog.ts index 047544e..82edfc2 100644 --- a/src/lib/changelog.ts +++ b/src/lib/changelog.ts @@ -23,6 +23,16 @@ export type ChangelogEntry = { }; export const CHANGELOG: readonly ChangelogEntry[] = [ + { + version: "1.7.1", + date: "2026-08-21", + summary: "Bilder zu den neuen Funktionen in der Bedienungsanleitung.", + changes: [ + "Die Bedienungsanleitung zeigt die Funktionen aus 1.7.0 jetzt auch im Bild: Logo im Code, Dublettenwarnung, Filter nach Art, Freigabe je Gruppe, Kurzhilfe und Domain-Bericht.", + "Alle übrigen Bilder mit erneuert — sie stammten noch aus 1.2.0.", + "Das Skript, das die Bilder erzeugt, lief seit einer Umbenennung ins Leere und tut es wieder.", + ], + }, { version: "1.7.0", date: "2026-08-21",