Teilen-Funktion: Öffentliche Share-Seite mit OG-Meta-Tags und Share-Button

- Öffentliche Route /e/{event} für Social-Media-Crawler (WhatsApp, Facebook)
- Share-View mit OG-Meta-Tags (Titel, Datum, Bild) für Link-Vorschau
- Teilen-Button auf Event-Detailseite (Web Share API + Clipboard-Fallback)
- Buttons: Teilen (helles Blau) + Bearbeiten (Standard-Blau)
- Hinweistext mit 3,5s Anzeige nach Link-Kopieren
- Event-Typ-Logos als neue Bilddateien

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Rhino
2026-03-03 11:53:11 +01:00
parent f9abc4561e
commit 7726fffb79
10 changed files with 214 additions and 3 deletions

View File

@@ -0,0 +1,138 @@
<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}" dir="{{ app()->getLocale() === 'ar' ? 'rtl' : 'ltr' }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>{{ $event->title }} - {{ $appName }}</title>
{{-- OG Meta Tags für Social-Media-Vorschau --}}
@php
$ogDescription = $event->start_at->translatedFormat(__('ui.date_format_long')) . ' ' . __('ui.clock');
if ($event->location_name) {
$ogDescription .= ' · ' . $event->location_name;
}
@endphp
<meta property="og:type" content="website">
<meta property="og:url" content="{{ route('events.share', $event) }}">
<meta property="og:title" content="{{ $event->title }}">
<meta property="og:description" content="{{ $ogDescription }}">
<meta property="og:image" content="{{ $event->imageUrl() }}">
<meta property="og:site_name" content="{{ $appName }}">
{{-- Twitter Card --}}
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{{ $event->title }}">
<meta name="twitter:description" content="{{ $ogDescription }}">
<meta name="twitter:image" content="{{ $event->imageUrl() }}">
<script src="https://cdn.tailwindcss.com"></script>
@include('components.tailwind-config')
@php $favicon = \App\Models\Setting::get('app_favicon'); @endphp
@if ($favicon)
<link rel="icon" href="{{ asset('storage/' . $favicon) }}">
@else
<link rel="icon" href="{{ asset('favicon.ico') }}">
@endif
</head>
<body class="min-h-screen bg-gray-100 flex flex-col">
<main class="flex-1 flex items-center justify-center px-4 py-12">
<div class="w-full max-w-md">
{{-- App-Logo --}}
<div class="text-center mb-6">
@php $logoLogin = \App\Models\Setting::get('app_logo_login'); @endphp
<img src="{{ $logoLogin ? asset('storage/' . $logoLogin) : asset('images/vereinos_logo.png') }}"
alt="{{ $appName }}" class="mx-auto h-16 mb-2 object-contain">
<p class="text-sm text-gray-500">{{ $appName }}</p>
</div>
<div class="bg-white rounded-lg shadow-md overflow-hidden">
{{-- Event-Bild --}}
<div class="flex justify-center bg-gray-50 py-6">
<img src="{{ $event->imageUrl() }}" alt="{{ $event->type->label() }}"
class="h-24 w-24 object-contain">
</div>
<div class="p-6">
{{-- Abgesagt-Banner --}}
@if ($event->status === \App\Enums\EventStatus::Cancelled)
<div class="bg-red-600 text-white text-center py-2 px-3 rounded-md mb-4 text-sm font-semibold">
{{ __('events.share_cancelled_notice') }}
</div>
@endif
{{-- Event-Typ Badge + Team --}}
<div class="mb-2">
<x-event-type-badge :type="$event->type" />
<span class="text-xs text-gray-500 ml-1">{{ $event->team->name }}</span>
</div>
{{-- Titel --}}
<h1 class="text-xl font-bold {{ $event->status === \App\Enums\EventStatus::Cancelled ? 'line-through text-gray-400' : 'text-gray-900' }}">
{{ $event->title }}
</h1>
{{-- Gegner und Ergebnis (bei Spielen) --}}
@if ($event->type->isGameType() && $event->opponent)
<p class="text-sm text-gray-600 mt-1">
{{ __('events.vs') }} {{ $event->opponent }}
@if ($event->hasScore())
<span class="font-semibold ml-2">{{ $event->scoreDisplay() }}</span>
@endif
</p>
@endif
{{-- Datum und Uhrzeit --}}
<div class="mt-4 space-y-2 text-sm text-gray-700">
<div class="flex items-center gap-2">
<svg class="w-4 h-4 text-gray-400 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
<span>
{{ $event->start_at->translatedFormat(__('ui.date_format_long')) }} {{ __('ui.clock') }}
@if ($event->end_at)
{{ $event->end_at->format('H:i') }} {{ __('ui.clock') }}
@endif
</span>
</div>
@if ($event->location_name || $event->address_text)
<div class="flex items-start gap-2">
<svg class="w-4 h-4 text-gray-400 mt-0.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
<div>
@if ($event->location_name)
<span class="font-medium">{{ $event->location_name }}</span>
@endif
@if ($event->location_name && $event->address_text)
<br>
@endif
@if ($event->address_text)
<span class="text-gray-500">{{ $event->address_text }}</span>
@endif
</div>
</div>
@endif
</div>
{{-- CTA-Button --}}
<div class="mt-6">
<a href="{{ route('events.show', $event) }}"
class="block w-full text-center bg-blue-600 text-white py-3 rounded-md font-medium hover:bg-blue-700 transition">
{{ __('events.share_view_in_app') }}
</a>
</div>
</div>
</div>
{{-- Footer --}}
<div class="text-center mt-6 text-sm text-gray-500">
<a href="/impressum" class="hover:underline">{{ __('ui.footer_impressum') }}</a>
<span class="mx-2">|</span>
<a href="/datenschutz" class="hover:underline">{{ __('ui.footer_privacy') }}</a>
</div>
</div>
</main>
</body>
</html>