presseportale/resources/views/components/layouts/auth/pressekonto.blade.php
Kevin Adametz 6c6b9e0f26 WS-2: Magic-Link für Firmen & Pressekontakte vereinheitlicht + Schreibzugriff
Magic-Link und Pressekontakt-Zugang zu einer Seite (/anmeldelink) zusammengeführt;
altes Login-Modal entfernt, /pressekontakt-zugang leitet weiter.

- ContactAccessService deckt jetzt Firmen-E-Mail UND Pressekontakt-E-Mail ab,
  portalübergreifend (ohne PortalScope). Eine E-Mail mehrfach hinterlegt → genau
  ein Account, dem alle Firmen + Kontakte zugeordnet werden.
- Zugeordnete Firmen erhalten Pivot-Rolle 'responsible' (Schreibzugriff auf
  Stammdaten, Kontakte, Pressemitteilungen) statt nur 'member'; bestehende
  Lese-Pivots werden hochgestuft, Owner bleiben unangetastet.
- Neuer Login-Listener (SyncCompanyMembershipsOnLogin) frischt die Zuordnungen
  bei JEDEM Login (Magic-Link, Passwort, Google) auf – auch nachträglich (API)
  hinzugekommene Firmen/Kontakte mit gleicher E-Mail greifen.
- Auth-Bereich erzwingt Hellmodus: aus dem Portal übernommene .dark-Klasse wird
  am <html> entfernt (Login war im Dark Mode hängengeblieben).
- Tests: Firmen-E-Mail-Login, Multi-Firmen-Aggregation, Schreibzugriff/Upgrade,
  Per-Login-Re-Sync, Auth-Hellmodus. Sicherheits-Doku aktualisiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:55:49 +00:00

160 lines
6.4 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@props([
'title' => null,
'eyebrow' => 'Publisher-Hub',
'heading' => null,
'topRightLabel' => null,
'topRightLinkText' => null,
'topRightLinkHref' => null,
'showFromBanner' => true,
])
@php
$brand = config('domains.domains.pressekonto.brand', []);
$from = request()->query('from');
$brandLabelMap = [
'presseecho' => 'presseecho.de',
'businessportal24' => 'businessportal24.com',
];
$fromBrandLabel = $brandLabelMap[$from] ?? null;
$pageTitle = $title ?? ($brand['meta_title'] ?? 'pressekonto Publisher-Hub');
config([
'app.theme' => 'pressekonto',
'app.view_prefix' => 'web',
]);
$themeCssPath = \App\Helpers\ThemeHelper::getThemeCssPath();
$assetsDir = config('domains.domains.pressekonto.assets_dir', 'build/web');
\App\Support\DomainAssetContext::configureVite([
'assets_dir' => $assetsDir,
]);
@endphp
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="csrf-token" content="{{ csrf_token() }}" />
{{-- Der Auth-Bereich ist bewusst immer im Hellmodus; Dark/Light gibt es nur
im eingeloggten Portal. Eine aus dem Portal übernommene .dark-Klasse
(flux_appearance-Cookie / DOM-Übernahme) wird hier sofort entfernt,
bevor die Styles greifen sonst erscheint der Login dunkel. --}}
<script>document.documentElement.classList.remove('dark');</script>
<title>{{ $pageTitle }}</title>
<link rel="icon" href="{{ asset(\App\Helpers\ThemeHelper::getFaviconPath()) }}" />
@include('partials.local-fonts')
{{-- Nur CSS aus dem Web-Build laden. Alpine bringt @livewireScripts mit;
würden wir hier zusätzlich resources/js/app.js mit Alpine.start()
laden, gäbe es zwei Alpine-Instanzen und wire:submit, x-data,
wire:model würden brechen (siehe Browser-Logs vor diesem Fix). --}}
@vite([$themeCssPath], $assetsDir)
@livewireStyles
</head>
<body class="font-sans text-ink antialiased" style="background-color: #f6f4ef;">
<div class="relative min-h-screen flex flex-col overflow-hidden bg-bg">
{{-- Atmosphäre: subtiles Raster --}}
<div class="auth-grid" aria-hidden="true"></div>
{{-- Atmosphäre: konzentrische Kreise um die Bildmitte --}}
<svg class="absolute inset-0 w-full h-full pointer-events-none" preserveAspectRatio="xMidYMid slice" viewBox="0 0 1280 880" aria-hidden="true">
<g opacity="0.09" stroke="#1A2540" fill="none" stroke-width="1">
<circle cx="640" cy="470" r="160" />
<circle cx="640" cy="470" r="260" />
<circle cx="640" cy="470" r="380" />
<circle cx="640" cy="470" r="510" />
<circle cx="640" cy="470" r="660" />
</g>
</svg>
{{-- 3px Hub-Blau-Streifen --}}
<div class="relative h-[3px] bg-hub z-10"></div>
{{-- Header --}}
<header class="relative z-10 px-6 sm:px-10 py-[22px] flex items-center justify-between gap-4">
<a href="{{ route('home') }}" class="flex items-baseline gap-2.5 no-underline" wire:navigate>
<span class="text-[19px] font-bold tracking-[-0.4px] leading-none">
<x-web.brand-mark brand="pressekonto" :serif="false" />
</span>
<span class="hidden sm:inline-block w-px h-[14px] bg-bg-rule"></span>
<span class="hidden sm:inline-block text-[9.5px] font-bold tracking-[0.22em] uppercase text-ink-3">
{{ $brand['tagline_short'] ?? 'Publisher · Hub' }}
</span>
</a>
@if ($topRightLinkText && $topRightLinkHref)
<span class="text-[13px] text-ink-3">
@if ($topRightLabel)
{{ $topRightLabel }}
@endif
<a href="{{ $topRightLinkHref }}" class="link-hub" wire:navigate>{{ $topRightLinkText }}</a>
</span>
@endif
</header>
{{-- Auth-Card --}}
<main class="relative z-10 flex-1 flex items-center justify-center px-6 sm:px-10 py-5">
<div class="w-full max-w-[440px]">
@if ($showFromBanner && $fromBrandLabel)
<div class="from-banner mb-3.5">
Sie kommen von <strong class="font-semibold text-ink">{{ $fromBrandLabel }}</strong>.
Ihr Konto funktioniert für <strong class="font-semibold text-ink">beide Portale</strong>
presseecho.de und businessportal24.com.
</div>
@elseif ($showFromBanner)
<div class="from-banner mb-3.5">
Ihr Konto funktioniert auch für
<strong class="font-semibold text-ink">presseecho.de</strong>
und
<strong class="font-semibold text-ink">businessportal24.com</strong>.
</div>
@endif
<div class="auth-card">
@if ($eyebrow)
<div class="eyebrow-hub mb-2.5">{{ $eyebrow }}</div>
@endif
@if ($heading)
<h1 class="text-[26px] font-bold tracking-[-0.5px] leading-[1.2] m-0 mb-7 text-ink">
{{ $heading }}
</h1>
@endif
{{ $slot }}
</div>
</div>
</main>
{{-- Micro-Footer --}}
<footer class="relative z-10 px-6 sm:px-10 pt-[18px] pb-[26px] flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 text-[11.5px] text-ink-3">
<span>
SSL <span class="text-ink-4 mx-2">·</span>
Daten in DE <span class="text-ink-4 mx-2">·</span>
2-Faktor verfügbar
</span>
<span class="flex flex-wrap items-center gap-x-1 gap-y-1">
<a href="{{ route('impressum') }}" class="text-ink-3 no-underline hover:text-ink">Impressum</a>
<span class="text-ink-4 mx-2">·</span>
<a href="{{ route('datenschutz') }}" class="text-ink-3 no-underline hover:text-ink">Datenschutz</a>
<span class="text-ink-4 mx-2">·</span>
<a href="{{ route('agb') }}" class="text-ink-3 no-underline hover:text-ink">AGB</a>
<span class="text-ink-4 mx-2">·</span>
<a href="{{ route('hilfe') }}" class="text-ink-3 no-underline hover:text-ink">Support</a>
</span>
</footer>
</div>
@livewireScripts
</body>
</html>