- Reworked the tenant admin login page
- Updated the User model to implement Filament’s tenancy contracts - Seeded a ready-to-use demo tenant (user, tenant, active package, purchase) - Introduced a branded, translated 403 error page to replace the generic forbidden message for unauthorised admin hits - Removed the public “Register” links from the marketing header - hardened join event logic and improved error handling in the guest pwa.
This commit is contained in:
197
app/Support/JoinTokenLayoutRegistry.php
Normal file
197
app/Support/JoinTokenLayoutRegistry.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
class JoinTokenLayoutRegistry
|
||||
{
|
||||
/**
|
||||
* Layout definitions for printable invite cards.
|
||||
*
|
||||
* @var array<string, array>
|
||||
*/
|
||||
private const LAYOUTS = [
|
||||
'modern-poster' => [
|
||||
'id' => 'modern-poster',
|
||||
'name' => 'Modern Poster',
|
||||
'subtitle' => 'Große, auffällige Fläche – perfekt für den Eingangsbereich.',
|
||||
'description' => 'Helle Posteroptik mit diagonalem Farbband und deutlicher Call-to-Action.',
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'background' => '#F8FAFC',
|
||||
'text' => '#0F172A',
|
||||
'accent' => '#6366F1',
|
||||
'secondary' => '#CBD5F5',
|
||||
'badge' => '#0EA5E9',
|
||||
'qr' => ['size_px' => 340],
|
||||
'svg' => ['width' => 1080, 'height' => 1520],
|
||||
'instructions' => [
|
||||
'Scanne den Code und tritt dem Event direkt bei.',
|
||||
'Speichere deine Lieblingsmomente mit Foto-Uploads.',
|
||||
'Merke dir dein Gäste-Pseudonym für Likes und Badges.',
|
||||
],
|
||||
],
|
||||
'elegant-frame' => [
|
||||
'id' => 'elegant-frame',
|
||||
'name' => 'Elegant Frame',
|
||||
'subtitle' => 'Ein ruhiges Layout mit Fokus auf Eleganz.',
|
||||
'description' => 'Serifen-Schrift, pastellige Flächen und dezente Rahmen für elegante Anlässe.',
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'background' => '#FBF7F2',
|
||||
'text' => '#2B1B13',
|
||||
'accent' => '#C08457',
|
||||
'secondary' => '#E6D5C3',
|
||||
'badge' => '#8B5CF6',
|
||||
'qr' => ['size_px' => 300],
|
||||
'svg' => ['width' => 1080, 'height' => 1520],
|
||||
'instructions' => [
|
||||
'QR-Code scannen oder Link im Browser eingeben.',
|
||||
'Name eingeben, Lieblingssprache auswählen und loslegen.',
|
||||
'Zeige diesen Druck am Empfang als Orientierung für Gäste.',
|
||||
],
|
||||
],
|
||||
'bold-gradient' => [
|
||||
'id' => 'bold-gradient',
|
||||
'name' => 'Bold Gradient',
|
||||
'subtitle' => 'Farbverlauf mit starkem Kontrast.',
|
||||
'description' => 'Ein kraftvolles Farbstatement mit großem QR-Code – ideal für Partys.',
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'background' => '#F97316',
|
||||
'background_gradient' => [
|
||||
'angle' => 190,
|
||||
'stops' => ['#F97316', '#EC4899', '#8B5CF6'],
|
||||
],
|
||||
'text' => '#FFFFFF',
|
||||
'accent' => '#FFFFFF',
|
||||
'secondary' => 'rgba(255,255,255,0.72)',
|
||||
'badge' => '#1E293B',
|
||||
'qr' => ['size_px' => 360],
|
||||
'svg' => ['width' => 1080, 'height' => 1520],
|
||||
'instructions' => [
|
||||
'Sofort scannen – der QR-Code führt direkt zum Event.',
|
||||
'Fotos knipsen, Challenges lösen und Likes sammeln.',
|
||||
'Teile den Link mit Freund:innen, falls kein Scan möglich ist.',
|
||||
],
|
||||
],
|
||||
'photo-strip' => [
|
||||
'id' => 'photo-strip',
|
||||
'name' => 'Photo Strip',
|
||||
'subtitle' => 'Layout mit Fotostreifen-Anmutung und Checkliste.',
|
||||
'description' => 'Horizontale Teilung, Platz für Hinweise und Storytelling.',
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'background' => '#FFFFFF',
|
||||
'text' => '#111827',
|
||||
'accent' => '#0EA5E9',
|
||||
'secondary' => '#94A3B8',
|
||||
'badge' => '#334155',
|
||||
'qr' => ['size_px' => 320],
|
||||
'svg' => ['width' => 1080, 'height' => 1520],
|
||||
'instructions' => [
|
||||
'Schritt 1: QR-Code scannen oder Kurzlink nutzen.',
|
||||
'Schritt 2: Profilname eingeben – kreativ sein!',
|
||||
'Schritt 3: Fotos hochladen und Teamaufgaben lösen.',
|
||||
],
|
||||
],
|
||||
'minimal-card' => [
|
||||
'id' => 'minimal-card',
|
||||
'name' => 'Minimal Card',
|
||||
'subtitle' => 'Kleine Karte – mehrfach druckbar als Tischaufsteller.',
|
||||
'description' => 'Schlichtes Kartenformat mit klarer Typografie und viel Weißraum.',
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'background' => '#F9FAFB',
|
||||
'text' => '#111827',
|
||||
'accent' => '#9333EA',
|
||||
'secondary' => '#E0E7FF',
|
||||
'badge' => '#64748B',
|
||||
'qr' => ['size_px' => 280],
|
||||
'svg' => ['width' => 1080, 'height' => 1520],
|
||||
'instructions' => [
|
||||
'Code scannen, Profil erstellen, Erinnerungen festhalten.',
|
||||
'Halte diese Karte an mehreren Stellen bereit.',
|
||||
'Für Ausdrucke auf 200 g/m² Kartenpapier empfohlen.',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Get layout definitions.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public static function all(): array
|
||||
{
|
||||
return array_values(array_map(fn ($layout) => self::normalize($layout), self::LAYOUTS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a layout definition.
|
||||
*/
|
||||
public static function find(string $id): ?array
|
||||
{
|
||||
$layout = self::LAYOUTS[$id] ?? null;
|
||||
|
||||
return $layout ? self::normalize($layout) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize and merge default values.
|
||||
*/
|
||||
private static function normalize(array $layout): array
|
||||
{
|
||||
$defaults = [
|
||||
'subtitle' => '',
|
||||
'description' => '',
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'background' => '#F9FAFB',
|
||||
'text' => '#0F172A',
|
||||
'accent' => '#6366F1',
|
||||
'secondary' => '#CBD5F5',
|
||||
'badge' => '#2563EB',
|
||||
'qr' => [
|
||||
'size_px' => 320,
|
||||
],
|
||||
'svg' => [
|
||||
'width' => 1080,
|
||||
'height' => 1520,
|
||||
],
|
||||
'background_gradient' => null,
|
||||
'instructions' => [],
|
||||
];
|
||||
|
||||
return array_replace_recursive($defaults, $layout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map layouts into an API-ready response structure, attaching URLs.
|
||||
*
|
||||
* @param callable(string $layoutId, string $format): string $urlResolver
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public static function toResponse(callable $urlResolver): array
|
||||
{
|
||||
return array_map(function (array $layout) use ($urlResolver) {
|
||||
$formats = ['pdf', 'svg'];
|
||||
|
||||
return [
|
||||
'id' => $layout['id'],
|
||||
'name' => $layout['name'],
|
||||
'description' => $layout['description'],
|
||||
'subtitle' => $layout['subtitle'],
|
||||
'preview' => [
|
||||
'background' => $layout['background'],
|
||||
'background_gradient' => $layout['background_gradient'],
|
||||
'accent' => $layout['accent'],
|
||||
'text' => $layout['text'],
|
||||
],
|
||||
'formats' => $formats,
|
||||
'download_urls' => collect($formats)
|
||||
->mapWithKeys(fn ($format) => [$format => $urlResolver($layout['id'], $format)])
|
||||
->all(),
|
||||
];
|
||||
}, self::all());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user