From 47cb9064e912f81cfce954cf5afa329a4765a7cb Mon Sep 17 00:00:00 2001 From: Benjamin Date: Wed, 18 Mar 2026 15:26:35 +0100 Subject: [PATCH 1/2] add legal information and a privacy policy; improve the layout and responsiveness --- README.md | 25 ++ app/datenschutz/page.tsx | 279 ++++++++++++++++++ app/impressum/page.tsx | 143 +++++++++ app/page.tsx | 6 +- components/export-dialog/ExportDialog.tsx | 4 +- components/site-footer.tsx | 22 +- .../SlidePreviewDialog.tsx | 2 +- components/song-form/SongMetadataCard.tsx | 98 +++--- components/song-form/SongTourOverlay.tsx | 2 +- components/song-form/utils.ts | 14 +- lib/legal.ts | 54 ++++ 11 files changed, 582 insertions(+), 67 deletions(-) create mode 100644 app/datenschutz/page.tsx create mode 100644 app/impressum/page.tsx create mode 100644 lib/legal.ts diff --git a/README.md b/README.md index 6ea1865..026a6ed 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,31 @@ pnpm dev Danach ist die Anwendung standardmäßig unter `http://localhost:3000` erreichbar. +## Rechtliche Angaben konfigurieren + +Für Impressum und Datenschutzerklärung können die Inhalte über Umgebungsvariablen angepasst werden. Dazu kann die Datei `.env.example` als Vorlage für eine lokale `.env.local` verwendet werden. + +Verfügbare Variablen: + +- `LEGAL_ENTITY_TYPE` mit `general` oder `church` +- `LEGAL_OWNER_NAME` +- `LEGAL_ADDRESS_LINE_1` +- `LEGAL_ADDRESS_LINE_2` +- `LEGAL_EMAIL` +- `LEGAL_PHONE` +- `LEGAL_WEBSITE_URL` +- `LEGAL_WEBSITE_LABEL` +- `LEGAL_VAT_ID` +- `LEGAL_RESPONSIBLE_FOR_CONTENT` +- `LEGAL_REPRESENTED_BY` +- `LEGAL_CHURCH_BODY` +- `LEGAL_CHURCH_SUPERVISORY_AUTHORITY` +- `LEGAL_HOSTING_PROVIDER` +- `LEGAL_HOSTING_LOG_RETENTION_DAYS` +- `LEGAL_EFFECTIVE_DATE` + +Standardmäßig ist der Betreiber-Typ neutral (`general`). Nur wenn `LEGAL_ENTITY_TYPE="church"` gesetzt ist, wird im Impressum ein zusätzlicher kirchenspezifischer Abschnitt angezeigt. So bleibt der Standardtext für nicht-kirchliche Betreiber unverändert. + ## Verfügbare Skripte ```bash diff --git a/app/datenschutz/page.tsx b/app/datenschutz/page.tsx new file mode 100644 index 0000000..cd9d0be --- /dev/null +++ b/app/datenschutz/page.tsx @@ -0,0 +1,279 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { ArrowLeft, ShieldCheck } from "lucide-react"; + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { legalConfig } from "@/lib/legal"; + +export const metadata: Metadata = { + title: "Datenschutzerklärung – SongBeamer Song-Editor", + description: + "Datenschutzerklärung des SongBeamer Song-Editors gemäß DSGVO und BDSG.", +}; + +export default function DatenschutzPage() { + return ( +
+
+ {/* Header */} +
+ + + Zurück + + +
+
+ +
+
+

+ Datenschutzerklärung +

+

+ Gemäß Art. 13 und 14 DSGVO · Stand: {legalConfig.effectiveDateLabel} +

+
+
+
+ + {/* 1. Verantwortlicher */} + + + 1. Verantwortlicher + + +

+ Verantwortlicher im Sinne der Datenschutz-Grundverordnung (DSGVO) und + des Bundesdatenschutzgesetzes (BDSG) ist: +

+

+ {legalConfig.ownerName} +
+ {legalConfig.addressLine1} +
+ {legalConfig.addressLine2} +
+ E-Mail: {legalConfig.email} +
+ Telefon: {legalConfig.phone} +

+

+ Ergänzende Anbieterangaben finden Sie im lokalen{" "} + + Impressum + . +

+
+
+ + {/* 2. Funktionsweise */} + + + 2. Funktionsweise der Anwendung + + +

+ Der SongBeamer Song-Editor ist eine vollständig im Browser ausgeführte + Webanwendung. Die Verarbeitung aller eingegebenen Lied- und Metadaten + findet ausschließlich lokal auf Ihrem Gerät statt. Es werden{" "} + + keine Eingabedaten an einen Server übermittelt + + , gespeichert oder an Dritte weitergegeben. +

+

+ Die Anwendung verwendet keine Cookies, keine lokale Browser-Speicherung + (localStorage / sessionStorage / IndexedDB) und keine + Sitzungsspeicherung für Nutzerdaten. +

+
+
+ + {/* 3. Hosting & Server-Logs */} + + + 3. Hosting und Server-Protokolldaten + + +

+ Diese Website wird auf Servern eines externen Hosting-Anbieters + betrieben. Beim Abrufen der Seiten werden durch den Hosting-Anbieter + automatisch Zugriffsdaten in Server-Protokolldateien gespeichert. + Dazu können gehören: +

+
    +
  • IP-Adresse des anfragenden Geräts
  • +
  • Datum und Uhrzeit des Zugriffs
  • +
  • Aufgerufene URL / Seite
  • +
  • Übertragene Datenmenge
  • +
  • Browsertyp und Betriebssystem (HTTP-Header)
  • +
+

+ Rechtsgrundlage für diese Verarbeitung ist Art. 6 Abs. 1 lit. f DSGVO + (berechtigtes Interesse an dem sicheren und fehlerfreien Betrieb der + Website). Die Protokolldaten werden nicht mit anderen + personenbezogenen Daten zusammengeführt und nach spätestens + {" "}{legalConfig.hostingRetentionDays} Tagen gelöscht, sofern keine gesetzliche Aufbewahrungspflicht + besteht. +

+

Aktuell eingesetzter Hosting-Anbieter: {legalConfig.hostingProvider}.

+
+
+ + {/* 4. Schriften */} + + + 4. Schriftarten + + +

+ Diese Anwendung verwendet die Schriftart Inter. Die Schrift + wird beim Build-Prozess heruntergeladen und von unserem Hosting-Server + ausgeliefert. Es findet beim Laden der Seite{" "} + + keine Verbindung zu Google-Servern + {" "} + statt. Es werden keine personenbezogenen Daten an Google oder andere + Dritte übermittelt. +

+
+
+ + {/* 5. Keine Tracking-Dienste */} + + + 5. Analyse- und Tracking-Dienste + + +

+ Diese Website verwendet{" "} + + keine Analyse-, Tracking- oder Werbedienste + {" "} + (z. B. Google Analytics, Matomo, Meta Pixel o. Ä.) und setzt{" "} + keine Cookies. Ein + Consent-Banner ist daher nicht erforderlich. +

+
+
+ + {/* 6. Externe Links */} + + + 6. Externe Links + + +

+ Diese Seite kann Links zu externen Websites enthalten, etwa zur + Hauptwebsite des Anbieters auf{" "} + + {legalConfig.websiteLabel} + + . Für die Inhalte und Datenschutzpraktiken dieser externen Seiten + ist der jeweilige Anbieter verantwortlich. Beim Anklicken eines + externen Links kann Ihre IP-Adresse an den jeweiligen Anbieter + übermittelt werden. +

+
+
+ + {/* 7. Betroffenenrechte */} + + + 7. Ihre Rechte als betroffene Person + + +

+ Ihnen stehen gemäß DSGVO folgende Rechte zu, soweit personenbezogene + Daten verarbeitet werden: +

+
    +
  • + Auskunftsrecht über + die zu Ihrer Person gespeicherten Daten (Art. 15 DSGVO) +
  • +
  • + Recht auf Berichtigung{" "} + unrichtiger Daten (Art. 16 DSGVO) +
  • +
  • + Recht auf Löschung{" "} + (Art. 17 DSGVO) +
  • +
  • + + Recht auf Einschränkung der Verarbeitung + {" "} + (Art. 18 DSGVO) +
  • +
  • + + Recht auf Datenübertragbarkeit + {" "} + (Art. 20 DSGVO) +
  • +
  • + Widerspruchsrecht{" "} + gegen die Verarbeitung (Art. 21 DSGVO) +
  • +
+

+ Zur Ausübung Ihrer Rechte wenden Sie sich bitte über die im{" "} + + Impressum + {" "} + angegebenen Kontaktdaten an den Verantwortlichen. +

+
+
+ + {/* 8. Beschwerderecht */} + + + 8. Beschwerderecht bei einer Aufsichtsbehörde + + +

+ Sie haben das Recht, sich bei einer Datenschutz-Aufsichtsbehörde über + die Verarbeitung Ihrer personenbezogenen Daten zu beschweren + (Art. 77 DSGVO). Die zuständige Aufsichtsbehörde richtet sich nach + Ihrem gewöhnlichen Aufenthaltsort, Ihrem Arbeitsort oder dem Ort des + mutmaßlichen Verstoßes. +

+
+
+ + {/* 9. Aktualität */} + + + 9. Aktualität und Änderung dieser Erklärung + + +

+ Diese Datenschutzerklärung hat den Stand {legalConfig.effectiveDateLabel}. Durch die + Weiterentwicklung der Anwendung oder geänderte gesetzliche Vorgaben + kann eine Anpassung erforderlich werden. Die jeweils aktuelle Fassung + ist stets unter dieser URL abrufbar. +

+
+
+
+
+ ); +} diff --git a/app/impressum/page.tsx b/app/impressum/page.tsx new file mode 100644 index 0000000..b6046a1 --- /dev/null +++ b/app/impressum/page.tsx @@ -0,0 +1,143 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { ArrowLeft, FileText } from "lucide-react"; + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { legalConfig } from "@/lib/legal"; + +export const metadata: Metadata = { + title: "Impressum – SongBeamer Song-Editor", + description: "Impressum des SongBeamer Song-Editors gemäß § 5 DDG und § 18 Abs. 2 MStV.", +}; + +export default function ImpressumPage() { + return ( +
+
+
+ + + Zurück + + +
+
+ +
+
+

+ Impressum +

+

+ Angaben gemäß § 5 DDG und § 18 Abs. 2 MStV +

+
+
+
+ + + + Angaben zum Anbieter + + +

+ {legalConfig.ownerName} +
+ {legalConfig.addressLine1} +
+ {legalConfig.addressLine2} +

+

+ E-Mail: {legalConfig.email} +
+ Telefon: {legalConfig.phone} +

+ {legalConfig.websiteUrl !== "https://example.com" && ( +

+ Website: {" "} + + {legalConfig.websiteLabel} + +

+ )} + {legalConfig.vatId &&

Umsatzsteuer-ID: {legalConfig.vatId}

} + {legalConfig.representedBy && ( +

Vertreten durch: {legalConfig.representedBy}

+ )} +
+
+ + {legalConfig.entityType === "church" && ( + + + Kirchlicher Träger + + +

+ Diese Website wird von einer kirchlichen Einrichtung oder + Kirchengemeinde bereitgestellt. +

+ {legalConfig.churchBody && ( +

Zugehörige Körperschaft / Träger: {legalConfig.churchBody}

+ )} + {legalConfig.churchSupervisoryAuthority && ( +

+ Zuständige kirchliche Aufsicht: {legalConfig.churchSupervisoryAuthority} +

+ )} +
+
+ )} + + + + Verantwortlich für den Inhalt + + +

+ Verantwortlich gemäß § 18 Abs. 2 MStV: +
+ {legalConfig.responsibleForContent} +
+ {legalConfig.addressLine1} +
+ {legalConfig.addressLine2} +

+
+
+ + + + Hinweis zur Kontaktaufnahme + + +

+ Bei Anfragen zu dieser Anwendung oder zu rechtlichen Angaben nutzen Sie + bitte die oben genannten Kontaktdaten. +

+
+
+ + + + Technische Entwicklung der Anwendung + + +

+ Die grundlegende technische Anwendung wurde entwickelt von {legalConfig.appDeveloper}. +

+
+
+
+
+ ); +} diff --git a/app/page.tsx b/app/page.tsx index 02e5333..a3c273a 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -10,11 +10,11 @@ export default function Home() { const [tourStartSignal, setTourStartSignal] = useState(0); return ( -
+
-
+
-

+

SongBeamer Song-Editor

diff --git a/components/export-dialog/ExportDialog.tsx b/components/export-dialog/ExportDialog.tsx index db51bb9..686a132 100644 --- a/components/export-dialog/ExportDialog.tsx +++ b/components/export-dialog/ExportDialog.tsx @@ -56,7 +56,7 @@ export function ExportDialog({ return (

- +
@@ -80,7 +80,7 @@ export function ExportDialog({
                         {sngContent}
                     
diff --git a/components/site-footer.tsx b/components/site-footer.tsx index 343693c..53d409b 100644 --- a/components/site-footer.tsx +++ b/components/site-footer.tsx @@ -3,30 +3,26 @@ import Link from "next/link"; export function SiteFooter() { return ( diff --git a/components/slide-preview-dialog/SlidePreviewDialog.tsx b/components/slide-preview-dialog/SlidePreviewDialog.tsx index 7f85032..1a6ed75 100644 --- a/components/slide-preview-dialog/SlidePreviewDialog.tsx +++ b/components/slide-preview-dialog/SlidePreviewDialog.tsx @@ -38,7 +38,7 @@ export function SlidePreviewDialog({ open, onOpenChange, data }: SlidePreviewDia return ( - +
diff --git a/components/song-form/SongMetadataCard.tsx b/components/song-form/SongMetadataCard.tsx index 6ff58b1..6426801 100644 --- a/components/song-form/SongMetadataCard.tsx +++ b/components/song-form/SongMetadataCard.tsx @@ -57,53 +57,59 @@ export function SongMetadataCard({ - - + - Allgemein - - - Copyright - - - Musik - - - Kategorien - - - Abspielreihenfolge - - - Erweitert - - + + Allgemein + + + Copyright + + + Musik + + + Kategorien + + + Abspielreihenfolge + + + Erweitert + + +
diff --git a/components/song-form/SongTourOverlay.tsx b/components/song-form/SongTourOverlay.tsx index d917cd6..5db757e 100644 --- a/components/song-form/SongTourOverlay.tsx +++ b/components/song-form/SongTourOverlay.tsx @@ -25,7 +25,7 @@ export function SongTourOverlay({

Produkttour • Schritt {tourStepIndex + 1} von {TOUR_STEPS.length} diff --git a/components/song-form/utils.ts b/components/song-form/utils.ts index 8e07165..4963ca0 100644 --- a/components/song-form/utils.ts +++ b/components/song-form/utils.ts @@ -2,13 +2,25 @@ import type { SongFormData } from "@/lib/sng/types"; export const DEFAULT_LANG_COUNT = 1; +function generateId(): string { + if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") { + return crypto.randomUUID(); + } + // Fallback for non-secure contexts (HTTP on mobile) + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0; + const v = c === "x" ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); +} + export function newSection( type: string, number: string, langCount: number ): SongFormData["sections"][number] { return { - id: crypto.randomUUID(), + id: generateId(), type: type as SongFormData["sections"][number]["type"], number, texts: Array.from({ length: Math.max(langCount, 1) }, () => ""), diff --git a/lib/legal.ts b/lib/legal.ts new file mode 100644 index 0000000..f70f95f --- /dev/null +++ b/lib/legal.ts @@ -0,0 +1,54 @@ +type LegalConfig = { + entityType: "general" | "church"; + ownerName: string; + appDeveloper: string; + addressLine1: string; + addressLine2: string; + email: string; + phone: string; + websiteUrl: string; + websiteLabel: string; + vatId: string; + responsibleForContent: string; + representedBy: string; + churchBody: string; + churchSupervisoryAuthority: string; + hostingProvider: string; + hostingRetentionDays: string; + effectiveDateLabel: string; +}; + +function readEnv(name: string, fallback: string): string { + const value = process.env[name]?.trim(); + return value && value.length > 0 ? value : fallback; +} + +function readEntityType(): "general" | "church" { + return readEnv("LEGAL_ENTITY_TYPE", "general") === "church" ? "church" : "general"; +} + +export const legalConfig: LegalConfig = { + entityType: readEntityType(), + ownerName: readEnv("LEGAL_OWNER_NAME", "Benjamin Esenwein"), + appDeveloper: "Benjamin Esenwein", + addressLine1: readEnv( + "LEGAL_ADDRESS_LINE_1", + "Bitte per Umgebungsvariable LEGAL_ADDRESS_LINE_1 setzen" + ), + addressLine2: readEnv( + "LEGAL_ADDRESS_LINE_2", + "Bitte per Umgebungsvariable LEGAL_ADDRESS_LINE_2 setzen" + ), + email: readEnv("LEGAL_EMAIL", "Bitte per Umgebungsvariable LEGAL_EMAIL setzen"), + phone: readEnv("LEGAL_PHONE", "Bitte per Umgebungsvariable LEGAL_PHONE setzen"), + websiteUrl: readEnv("LEGAL_WEBSITE_URL", "https://example.com"), + websiteLabel: readEnv("LEGAL_WEBSITE_LABEL", "example.com"), + vatId: readEnv("LEGAL_VAT_ID", ""), + responsibleForContent: readEnv("LEGAL_RESPONSIBLE_FOR_CONTENT", "Benjamin Esenwein"), + representedBy: readEnv("LEGAL_REPRESENTED_BY", ""), + churchBody: readEnv("LEGAL_CHURCH_BODY", ""), + churchSupervisoryAuthority: readEnv("LEGAL_CHURCH_SUPERVISORY_AUTHORITY", ""), + hostingProvider: readEnv("LEGAL_HOSTING_PROVIDER", "Ihr Hosting-Anbieter"), + hostingRetentionDays: readEnv("LEGAL_HOSTING_LOG_RETENTION_DAYS", "30"), + effectiveDateLabel: readEnv("LEGAL_EFFECTIVE_DATE", "März 2026"), +}; From d35ef651100596811f9a2429fcb1dde74d9fba09 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Wed, 18 Mar 2026 15:27:06 +0100 Subject: [PATCH 2/2] 1.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9bb1051..242116c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sng-creator", - "version": "1.0.2", + "version": "1.1.0", "private": true, "scripts": { "dev": "next dev",