- 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.
197 lines
7.2 KiB
PHP
197 lines
7.2 KiB
PHP
<?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());
|
||
}
|
||
} |