L15 Testphase & Onboarding: Selbstanmeldung mit Double-Opt-in, Plattform-Wizard, Nur-Lesen-Sperre, Export, Lebenszyklus-Job

- Datenmodell: Testphasen-Lebenszyklus am Mandanten (plan, trialEndsAt, readOnlySince, deletionDueAt,
  Versandmarker), TrialSignup (Plattform, Hashes statt Klartext), TenantExport (RLS), Onboarding-Status
- /testen: 5-Schritte-Wizard (Betrieb, Admin-Konto, Enddatum, Einrichtung, Zusammenfassung),
  Bestätigung per POST, direkte Anmeldung über login-ticket; Rate-Limit je IP/E-Mail, Honeypot,
  Enumeration-Schutz, Slug-Kollisionen
- Plattform: Wizard „Testmandant anlegen“ mit Einladung, Badges/Filter, Enddatum ändern,
  umwandeln, beenden, Löschung vormerken/abbrechen (Bestätigung + Audit)
- Schreibsperre nach Ablauf zentral in moduleGuard und requireApiContext (non-GET über withApi),
  Upload-Routen, Einstellungen/Nutzerverwaltung, Worker-Jobs; Banner Backoffice + mobil
- Datenexport (ZIP mit CSV/JSON + Dateien) als Worker-Job, auch im Nur-Lesen-Zustand
- Täglicher Job trial-lifecycle: Erinnerungen 7/3/1, Ablauf, Löschhinweis, Löschung über das Offboarding
- Erste-Schritte-Checkliste im Dashboard, Mail-Vorlagen de/en, Tests + Smoke, Betriebsdoku

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 19:01:47 +02:00
co-authored by Claude Opus 5
parent 6b8cdf543b
commit d9290a187c
79 changed files with 4576 additions and 19 deletions
+269
View File
@@ -0,0 +1,269 @@
{
"meta": {
"title": "Kostenlos testen",
"confirmTitle": "Testphase bestätigen",
"termsTitle": "Nutzungsbedingungen",
"privacyTitle": "Datenschutz"
},
"public": {
"heading": "Craftvia kostenlos testen",
"intro": "In wenigen Minuten eingerichtet. Ohne Zahlungsdaten, ohne automatische Verlängerung.",
"benefit1": "Aufträge planen, Einsätze mobil erfassen, Berichte mit Unterschrift",
"benefit2": "Enddatum frei wählen – danach bleiben Ihre Daten lesbar und exportierbar",
"benefit3": "Auf Wunsch mit Beispieldaten zum sofortigen Ausprobieren",
"haveAccount": "Schon einen Zugang?",
"login": "Anmelden",
"terms": "Nutzungsbedingungen",
"privacy": "Datenschutz"
},
"steps": {
"company": "Betrieb",
"account": "Admin-Konto",
"period": "Testzeitraum",
"setup": "Einrichtung",
"summary": "Zusammenfassung"
},
"progress": "Schritt {current} von {total}",
"progressLabel": "Fortschritt",
"fields": {
"companyName": "Firmenname",
"sector": "Branche",
"sectorChoose": "Bitte wählen (optional)",
"sectorOther": "Branche (Freitext)",
"companySize": "Betriebsgröße",
"companySizeNone": "Keine Angabe",
"adminName": "Ihr Name",
"email": "E-Mail-Adresse",
"emailHint": "Die Adresse ist Ihr Anmeldename. Wir senden Ihnen einen Bestätigungslink.",
"password": "Passwort",
"passwordHint": "Anforderungen: {policy}",
"showPassword": "Passwort anzeigen",
"trialEndDate": "Testphase bis",
"sampleData": "Mit Beispieldaten starten",
"sampleDataHint": "Drei Kunden, Objekte, ein Team und vier Aufträge in verschiedenen Zuständen. Sie können sie jederzeit löschen.",
"modules": "Module",
"modulesHint": "Alle Module sind vorausgewählt. Nicht benötigte Module können Sie abwählen – später lässt sich das ändern.",
"acceptTerms": "Ich akzeptiere die {link}.",
"acceptPrivacy": "Ich habe die {link} gelesen.",
"required": "Pflichtfeld",
"website": "Website (bitte leer lassen)"
},
"sectors": {
"shk": "Sanitär, Heizung, Klima",
"electrical": "Elektro",
"roofing": "Dach und Fassade",
"carpentry": "Tischlerei und Innenausbau",
"painting": "Maler und Lackierer",
"facility": "Gebäudetechnik und Facility",
"construction": "Bau und Montage",
"metal": "Metallbau",
"other": "Andere Branche"
},
"sizes": {
"1_5": "1–5 Beschäftigte",
"6_20": "6–20 Beschäftigte",
"21_50": "21–50 Beschäftigte",
"51_200": "51–200 Beschäftigte",
"200_": "mehr als 200 Beschäftigte"
},
"period": {
"until": "Testphase bis {date} ({days, plural, one {# Tag} other {# Tage}})",
"range": "Möglich: {min} bis {max}",
"afterEnd": "Nach dem Enddatum bleiben Ihre Daten lesbar und exportierbar. Ohne Umwandlung in die Vollversion löschen wir sie 30 Tage später."
},
"summary": {
"intro": "Bitte prüfen Sie Ihre Angaben.",
"sampleYes": "mit Beispieldaten",
"sampleNo": "ohne Beispieldaten",
"modulesCount": "{count} von {total} Modulen",
"edit": "Ändern",
"noSector": "keine Branche angegeben"
},
"actions": {
"back": "Zurück",
"next": "Weiter",
"submit": "Testphase starten",
"submitting": "Wird gesendet …",
"checking": "Wird geprüft …"
},
"sent": {
"title": "Fast geschafft – bitte E-Mail bestätigen",
"body": "Wir haben Ihnen eine E-Mail geschickt. Klicken Sie auf den Link darin, um Ihre Testphase zu starten.",
"hint": "Der Link ist 24 Stunden gültig. Keine E-Mail erhalten? Prüfen Sie den Spam-Ordner oder starten Sie die Anmeldung erneut.",
"again": "Anmeldung neu starten"
},
"errors": {
"company_required": "Bitte den Firmennamen angeben (mindestens 2 Zeichen).",
"too_long": "Die Eingabe ist zu lang.",
"invalid_choice": "Bitte einen Eintrag aus der Liste wählen.",
"name_required": "Bitte Ihren Namen angeben.",
"email_invalid": "Bitte eine gültige E-Mail-Adresse angeben.",
"password_policy": "Das Passwort erfüllt die Anforderungen nicht.",
"date_invalid": "Bitte ein gültiges Datum wählen.",
"date_too_early": "Das Enddatum muss frühestens morgen sein.",
"date_too_late": "Das Enddatum liegt zu weit in der Zukunft.",
"modules_required": "Bitte mindestens ein Modul auswählen.",
"terms_required": "Bitte die Nutzungsbedingungen akzeptieren.",
"privacy_required": "Bitte die Datenschutzhinweise bestätigen.",
"invalid_request": "Die Anfrage war unvollständig. Bitte Seite neu laden.",
"rate_limited": "Zu viele Versuche. Bitte in einigen Minuten erneut versuchen.",
"failed": "Das hat nicht geklappt. Bitte erneut versuchen."
},
"confirm": {
"title": "Testphase starten",
"intro": "Bestätigen Sie Ihre Anmeldung. Danach richten wir Ihren Zugang ein und melden Sie direkt an.",
"company": "Betrieb",
"until": "Testphase bis",
"sampleData": "Beispieldaten",
"yes": "ja",
"no": "nein",
"button": "Jetzt einrichten",
"pending": "Wird eingerichtet …",
"invalid": "Der Link ist ungültig oder wurde bereits verwendet.",
"expired": "Der Link ist abgelaufen. Bitte starten Sie die Anmeldung erneut.",
"rate_limited": "Zu viele Versuche. Bitte in einigen Minuten erneut versuchen.",
"toSignup": "Zur Anmeldung „Kostenlos testen“",
"toLogin": "Zum Login"
},
"legal": {
"back": "Zurück zur Anmeldung",
"placeholder": "Platzhalter – der verbindliche Text wird vom Betreiber vor dem Go-live eingesetzt.",
"termsBody1": "Die Testphase ist kostenlos und endet automatisch zum gewählten Datum. Es entsteht kein Vertrag über eine kostenpflichtige Nutzung.",
"termsBody2": "Nach dem Ende sind die Daten 30 Tage lesbar und exportierbar. Ohne Umwandlung in die Vollversion werden sie danach gelöscht.",
"privacyBody1": "Wir verarbeiten Ihre Angaben (Firmenname, Name, E-Mail-Adresse) zur Einrichtung und Betreuung der Testphase. Passwörter speichern wir ausschließlich als sicheren Hash.",
"privacyBody2": "Nicht bestätigte Anmeldungen löschen wir nach wenigen Tagen. Daten der Testphase werden nach Ablauf gemäß den Nutzungsbedingungen gelöscht."
},
"banner": {
"endsIn": "{days, plural, =0 {Testphase endet heute.} =1 {Testphase endet morgen.} other {Testphase endet in # Tagen.}}",
"endsOn": "Letzter Tag: {date}.",
"expired": "Testphase abgelaufen – nur Lesezugriff.",
"deletion": "Daten werden am {date} gelöscht.",
"contact": "Vollversion oder Verlängerung: {email}",
"contactGeneric": "Vollversion oder Verlängerung? Wenden Sie sich an Ihren Ansprechpartner bei Craftvia.",
"export": "Daten exportieren"
},
"onboarding": {
"title": "Erste Schritte",
"welcome": "Willkommen! Ihre Testphase ist eingerichtet.",
"progress": "{done} von {total} erledigt",
"hide": "Ausblenden",
"markDone": "Als erledigt markieren",
"markOpen": "Wieder öffnen",
"open": "Öffnen",
"auto": "erkannt",
"doneLabel": "Erledigt",
"openLabel": "Offen",
"items": {
"company": { "title": "Firmendaten prüfen", "text": "Adresse, Telefon und E-Mail erscheinen auf Berichten." },
"team": { "title": "Team anlegen", "text": "Teams bündeln Monteure für Planung und Zuweisung." },
"technician": { "title": "Monteur einladen", "text": "Ein Einladungslink genügt – das Passwort vergibt die Person selbst." },
"first_order": { "title": "Ersten Auftrag anlegen oder importieren", "text": "Manuell erfassen oder ein Auftrags-PDF hochladen." },
"mobile": { "title": "Mobile App öffnen", "text": "Auf dem Smartphone /m aufrufen und zum Startbildschirm hinzufügen." }
}
},
"export": {
"crumb": "Organisation",
"title": "Datenexport",
"sub": "Alle Daten Ihres Betriebs als ZIP-Datei",
"contents": "Enthalten: Kunden, Ansprechpartner, Objekte, Teams, Aufträge, Zeiten, Material, Berichte, Meilensteine und Abrechnung als CSV und JSON sowie alle gespeicherten Dateien (Dokumente, Berichts-PDFs, Fotos).",
"readOnlyHint": "Der Export ist auch nach Ablauf der Testphase möglich.",
"request": "Export erstellen",
"requested": "Export angefordert. Die Datei steht in wenigen Minuten bereit – laden Sie die Seite dann neu.",
"listTitle": "Letzte Exporte",
"empty": "Noch kein Export erstellt.",
"created": "Angefordert",
"state": "Status",
"file": "Datei",
"status": { "queued": "Wartet", "running": "Wird erstellt", "done": "Bereit", "failed": "Fehlgeschlagen", "expired": "Abgelaufen" },
"download": "Herunterladen",
"expires": "bis {date}",
"errors": {
"export_running": "Es läuft bereits ein Export. Bitte warten Sie, bis er fertig ist.",
"failed": "Der Export konnte nicht angefordert werden."
}
},
"platform": {
"newTrial": "Testmandant anlegen",
"filter": "Filter",
"filterAll": "Alle",
"filterTrial": "Test",
"filterFull": "Voll",
"colPlan": "Version",
"badgeFull": "Vollversion",
"badgeUntil": "Test bis {date}",
"badgeExpired": "abgelaufen – nur lesen",
"badgeDeletion": "Löschung am {date}",
"badgeDeleted": "gelöscht",
"wizard": {
"crumb": "Plattform-Betrieb",
"title": "Testmandant anlegen",
"sub": "Mandant mit Enddatum anlegen; der Admin erhält eine Einladung zum Passwort-Setzen.",
"sectionCompany": "1. Betrieb",
"sectionAdmin": "2. Administrator",
"sectionPeriod": "3. Testzeitraum",
"sectionSetup": "4. Einrichtung",
"companyName": "Firmenname",
"sector": "Branche (optional)",
"adminName": "Name",
"adminEmail": "E-Mail-Adresse",
"adminHint": "Neue Adressen erhalten den bestehenden Einladungslink (7 Tage gültig). Bestehende Zugänge werden nur verknüpft.",
"endDate": "Testphase bis",
"endDateHint": "Frei wählbar bis ein Jahr im Voraus.",
"sampleData": "Mit Beispieldaten",
"submit": "Testmandant anlegen",
"submitting": "Wird angelegt …",
"cancel": "Abbrechen"
},
"card": {
"title": "Testphase",
"plan": "Version",
"until": "Letzter Tag",
"state": "Zustand",
"stateActive": "läuft",
"stateExpired": "abgelaufen – nur lesen",
"stateConverted": "in Vollversion umgewandelt",
"stateDeleted": "gelöscht",
"deletion": "Löschung",
"deletionNone": "nicht vorgemerkt",
"source": "Herkunft",
"sourceSelf": "Selbstanmeldung",
"sourcePlatform": "Plattform-Wizard",
"extend": "Enddatum ändern / verlängern",
"convert": "In Vollversion umwandeln",
"end": "Testphase sofort beenden",
"schedule": "Löschung vormerken",
"cancel": "Löschung abbrechen",
"created": "Testmandant angelegt.",
"invited": "Die Einladung an den Administrator wurde verschickt.",
"done": "Änderung gespeichert."
},
"ops": {
"extendTitle": "Enddatum ändern",
"extendText": "Neues letztes Testdatum. Liegt es in der Zukunft, ist der Mandant sofort wieder schreibbar; Erinnerungen werden neu geplant.",
"convertTitle": "In Vollversion umwandeln",
"convertText": "Der Mandant bleibt dauerhaft schreibbar und wird nie automatisch gelöscht.",
"endTitle": "Testphase sofort beenden",
"endText": "Der Mandant ist ab sofort nur noch lesbar. Eine vorgemerkte Löschung verschiebt sich auf 30 Tage ab heute.",
"scheduleTitle": "Löschung vormerken",
"scheduleText": "Löschung 30 Tage nach Ende, frühestens in 7 Tagen (Hinweismail an die Admins).",
"cancelTitle": "Löschung abbrechen",
"cancelText": "Der Mandant wird nicht automatisch gelöscht. Er bleibt nach Ablauf nur lesbar.",
"confirm": "Ich bestätige diese Änderung.",
"submit": "Ausführen",
"submitting": "Wird ausgeführt …",
"close": "Schließen"
},
"errors": {
"confirm_required": "Bitte die Bestätigung ankreuzen.",
"invalid_input": "Bitte die Eingaben prüfen.",
"date_invalid": "Bitte ein gültiges Datum wählen.",
"date_in_past": "Das Enddatum darf nicht in der Vergangenheit liegen.",
"date_too_late": "Das Enddatum liegt mehr als ein Jahr in der Zukunft.",
"not_a_trial": "Dieser Mandant ist keine Testversion.",
"tenant_deleted": "Der Mandant wurde bereits gelöscht.",
"platform_admin_required": "Nur Plattform-Voll-Administratoren dürfen das.",
"tenant not found": "Mandant nicht gefunden.",
"failed": "Die Aktion ist fehlgeschlagen."
}
}
}