diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 5373f35..987d56d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -25,7 +25,8 @@ "Bash(npx playwright *)", "Bash(npm init *)", "Bash(npm install *)", - "Bash(NODE_TLS_REJECT_UNAUTHORIZED=0 node test.mjs)" + "Bash(NODE_TLS_REJECT_UNAUTHORIZED=0 node test.mjs)", + "Bash(open *)" ] } } diff --git a/CLAUDE.md b/CLAUDE.md index 20b8b90..eaf12cd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -338,6 +338,16 @@ Billable/Non-Billable Trennung via `Service.billable`. Revenue-Berechnung nutzt - **Brand-Farbe**: Billable-Balken nutzen `--color-primary`, Non-Billable grau - **Donut-Charts**: Drei nebeneinander (3-Spalten-Grid, responsive 1-Spalte), eigene Legende mit Farb-Dot, Name, Wert und Prozent. 8-Farben-Palette, „Rest" in Grau +## Account-Finder (Anmelde-Übergangsseite) + +Seite unter `/find-account` zur Weiterleitung auf die richtige Subdomain-Login-Seite. Nutzt das Register-Card-Design. + +- **Route**: `/find-account` (`app_find_account`) in `RegistrationController`, PUBLIC_ACCESS +- **Template**: `templates/registration/find_account.html.twig` (embed von `register-card.html.twig`) +- **Styles**: `.find-account-input` in `_register.scss` (Input mit `.domain`-Suffix) +- **Slug-Validierung**: `POST /api/check-slug` prüft via `AccountRepository::findBySlug()` ob der Account existiert, bevor weitergeleitet wird. Fehlermeldung bei unbekanntem Slug. +- **Links**: Register-Seite verlinkt „Zur Anmeldung" auf `/find-account`, Find-Account verlinkt „Jetzt registrieren" auf `/register` + ## mite-Import Import von Kunden, Projekten, Leistungen und Zeiteinträgen aus mite-Backups (XML). Erreichbar als eigener Tab in den Account-Einstellungen (nur Admin). diff --git a/httpdocs/assets/styles/components/_register.scss b/httpdocs/assets/styles/components/_register.scss index 9ad4093..34c30e7 100644 --- a/httpdocs/assets/styles/components/_register.scss +++ b/httpdocs/assets/styles/components/_register.scss @@ -150,6 +150,38 @@ } } +// ─── Find Account ──────────────────────────────────────────────────────── +.find-account-input { + display: flex; + align-items: center; + gap: 0; + border: 1px solid $color-input-border; + border-radius: $radius-sm; + background: $color-input-bg; + transition: border-color $transition-fast; + + &:focus-within { + border-color: var(--color-primary); + box-shadow: $shadow-focus; + } +} + +.find-account-input__field { + flex: 1; + border: none !important; + box-shadow: none !important; + background: transparent; + min-width: 0; +} + +.find-account-input__suffix { + padding: 0 $space-4 0 0; + font-size: $font-size-sm; + color: $color-text-muted; + white-space: nowrap; + user-select: none; +} + // ─── Erfolgs-State ──────────────────────────────────────────────────────────── .register-success { text-align: center; diff --git a/httpdocs/config/packages/security.yaml b/httpdocs/config/packages/security.yaml index 3040cf8..2821df2 100644 --- a/httpdocs/config/packages/security.yaml +++ b/httpdocs/config/packages/security.yaml @@ -48,7 +48,9 @@ security: access_control: - { path: ^/login, roles: PUBLIC_ACCESS } - { path: ^/register, roles: PUBLIC_ACCESS } + - { path: ^/find-account, roles: PUBLIC_ACCESS } - { path: ^/api/register, roles: PUBLIC_ACCESS } + - { path: ^/api/check-slug, roles: PUBLIC_ACCESS } - { path: ^/verify/, roles: PUBLIC_ACCESS } - { path: ^/invite/, roles: PUBLIC_ACCESS } - { path: ^/forgot-password, roles: PUBLIC_ACCESS } diff --git a/httpdocs/src/Controller/RegistrationController.php b/httpdocs/src/Controller/RegistrationController.php index 15052e8..1460e44 100644 --- a/httpdocs/src/Controller/RegistrationController.php +++ b/httpdocs/src/Controller/RegistrationController.php @@ -2,6 +2,7 @@ namespace App\Controller; +use App\Repository\Central\AccountRepository; use App\Service\RegistrationService; use App\Service\SlugGenerator; use Psr\Log\LoggerInterface; @@ -17,6 +18,7 @@ class RegistrationController extends AbstractController public function __construct( private readonly RegistrationService $registrationService, private readonly SlugGenerator $slugGenerator, + private readonly AccountRepository $accountRepo, private readonly TranslatorInterface $translator, private readonly string $appDomain, private readonly LoggerInterface $logger, @@ -30,9 +32,32 @@ class RegistrationController extends AbstractController ]); } + #[Route('/find-account', name: 'app_find_account')] + public function findAccount(): Response + { + return $this->render('registration/find_account.html.twig', [ + 'appDomain' => $this->appDomain, + ]); + } + /** * Live-Vorschau des Slugs während der User tippt. */ + #[Route('/api/check-slug', name: 'api_check_slug', methods: ['POST'])] + public function checkSlug(Request $request): JsonResponse + { + $data = json_decode($request->getContent(), true); + $slug = trim($data['slug'] ?? ''); + + if ($slug === '') { + return $this->json(['exists' => false]); + } + + $account = $this->accountRepo->findBySlug($slug); + + return $this->json(['exists' => $account !== null]); + } + #[Route('/api/register/preview-slug', name: 'api_register_preview_slug', methods: ['POST'])] public function previewSlug(Request $request): JsonResponse { diff --git a/httpdocs/templates/registration/find_account.html.twig b/httpdocs/templates/registration/find_account.html.twig new file mode 100644 index 0000000..cc6447f --- /dev/null +++ b/httpdocs/templates/registration/find_account.html.twig @@ -0,0 +1,95 @@ +{# templates/registration/find_account.html.twig #} + + + + + + {{ 'app.find_account.page_title'|trans }} + {{ encore_entry_link_tags('app') }} + + + +{% embed '_components/register-card.html.twig' %} + {% block content %} + +
+ spawntree Timetracker +
+

{{ 'app.find_account.title'|trans }}

+

{{ 'app.find_account.subtitle'|trans }}

+ + + +
+
+ + +
+ +
+ + +
+
+ + {% endblock %} +{% endembed %} + + + + + diff --git a/httpdocs/templates/registration/register.html.twig b/httpdocs/templates/registration/register.html.twig index 2250367..cf1f54d 100644 --- a/httpdocs/templates/registration/register.html.twig +++ b/httpdocs/templates/registration/register.html.twig @@ -81,7 +81,7 @@ {{ 'app.register.btn_submit'|trans }}

- {{ 'app.register.already_registered'|trans }} {{ 'app.register.link_login'|trans }} + {{ 'app.register.already_registered'|trans }} {{ 'app.register.link_login'|trans }}

diff --git a/httpdocs/translations/messages.de.yaml b/httpdocs/translations/messages.de.yaml index 5e0d59f..b9804c2 100644 --- a/httpdocs/translations/messages.de.yaml +++ b/httpdocs/translations/messages.de.yaml @@ -413,6 +413,19 @@ app: already_registered: "Bereits registriert?" link_login: "Zur Anmeldung" + find_account: + page_title: "Anmelden – spawntree Timetracker" + title: "Anmelden" + subtitle: "Gib den Namen deines Accounts ein, um zur Anmeldeseite weitergeleitet zu werden." + label_slug: "Account-Name" + placeholder_slug: "meine-firma" + btn_submit: "Weiter zur Anmeldung" + error_empty: "Bitte gib deinen Account-Namen ein." + error_invalid: "Der Account-Name darf nur Kleinbuchstaben, Zahlen und Bindestriche enthalten." + error_not_found: "Dieser Account wurde nicht gefunden. Bitte überprüfe den Namen." + no_account: "Noch kein Konto?" + link_register: "Jetzt registrieren" + confirm_error: page_title: "Fehler – spawntree Timetracker" title: "Link ungültig"