add success page #27

Merged
csimonis merged 4 commits from feature/success-page into main 2025-02-23 16:39:03 +00:00
5 changed files with 170 additions and 34 deletions

3
.ddev/commands/host/cf Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
cloudflared tunnel --url "https://127.0.0.1/$DDEV_HOST_HTTPS_PORT" --http-host-header "$DDEV_SITENAME.$DDEV_TLD"

View File

@ -10,11 +10,32 @@ use Symfony\Component\Routing\Attribute\Route;
class SuccessController extends AbstractController class SuccessController extends AbstractController
{ {
#[Route(path: '/success', name: 'app_success_page', methods: Request::METHOD_GET)] #[Route(path: '/success', name: 'app_order_success', methods: Request::METHOD_GET)]
public function __invoke(TicketService $service, Request $request): Response public function success(Request $request, TicketService $service): Response
{ {
$service->completePayment((string)$request->query->get('session_id')); $sessionId = $request->query->get('session_id');
if (!$sessionId) {
noty()->error('Etwas ist schiefgelaufen');
return $this->redirectToRoute('app_ticket');
}
if (!$service->completePayment($sessionId)) {
noty()->error('Etwas ist schiefgelaufen');
return $this->redirectToRoute('app_ticket');
}
return $this->render('ticket/success.html.twig'); return $this->render('ticket/success.html.twig');
} }
#[Route(path: '/cancelled', name: 'app_cancelled', methods: Request::METHOD_GET)]
public function cancel(): Response
{
noty()->error('Bezahlung abgebrochen');
return $this->redirectToRoute('app_ticket');
}
} }

View File

@ -3,8 +3,8 @@
namespace App\Controller; namespace App\Controller;
use App\DataObjects\TicketFormData; use App\DataObjects\TicketFormData;
use App\Service\EventService;
use App\Service\TicketService; use App\Service\TicketService;
use Stripe\Stripe;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@ -16,6 +16,7 @@ final class TicketController extends AbstractController
public function __construct( public function __construct(
private TicketService $service, private TicketService $service,
private SerializerInterface $serializer, private SerializerInterface $serializer,
private EventService $eventService,
) { ) {
} }
@ -33,31 +34,14 @@ final class TicketController extends AbstractController
return $this->json(['id' => $this->service->handleTicketData($ticketData)->id]); return $this->json(['id' => $this->service->handleTicketData($ticketData)->id]);
} }
#[Route(path: '/success', name: 'app_order_success', methods: Request::METHOD_GET)] #[Route(path: '/calendar', name: 'app_calendar')]
public function success(Request $request): Response public function calendar(Request $request): Response
{ {
$sessionId = $request->query->get('session_id'); $userAgent = $request->headers->get('User-Agent');
$isApple = str_contains($userAgent, 'Mac') || str_contains($userAgent, 'iPhone') || str_contains($userAgent, 'iPad');
if (!$sessionId) { return $isApple
noty()->error('Etwas ist schiefgelaufen'); ? $this->eventService->generateIcs()
: $this->eventService->generateGoogleRedirect();
return $this->redirectToRoute('app_ticket');
}
if (!$this->service->completePayment($sessionId)) {
noty()->error('Etwas ist schiefgelaufen');
return $this->redirectToRoute('app_ticket');
}
return $this->redirectToRoute('app_success_page');
}
#[Route(path: '/cancelled', name: 'app_cancelled', methods: Request::METHOD_GET)]
public function cancel(): Response
{
noty()->error('Bezahlung abgebrochen');
return $this->redirectToRoute('app_ticket');
} }
} }

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace App\Service;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
class EventService
{
private const EVENT_DATA = [
'title' => 'Abiball 2025',
'description' => 'Der Abiball der Freien Waldorfschule Bremen Osterholz und Touler Straße',
'location' => 'Graubündener Str. 4, 28325 Bremen',
'start' => '20250628T173000',
'end' => '20250629T030000',
];
public function generateIcs(): Response
{
$data = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'BEGIN:VEVENT',
'DTSTART:' . self::EVENT_DATA['start'],
'DTEND:' . self::EVENT_DATA['end'],
'SUMMARY:' . self::EVENT_DATA['title'],
'DESCRIPTION:' . self::EVENT_DATA['description'],
'LOCATION:' . self::EVENT_DATA['location'],
'END:VEVENT',
'END:VCALENDAR'
];
return new Response(implode("\r\n", $data), headers: [
'Content-Type' => 'text/calendar; charset=utf-8',
'Content-Disposition' => 'attachment; filename="event.ics"'
]);
}
public function generateGoogleRedirect(): Response
{
return new RedirectResponse('https://calendar.google.com/calendar/render?' . http_build_query([
'action' => 'TEMPLATE',
'text' => self::EVENT_DATA['title'],
'dates' => self::EVENT_DATA['start'] . '/' . self::EVENT_DATA['end'],
'details' => self::EVENT_DATA['description'],
'location' => self::EVENT_DATA['location'],
]));
}
}

View File

@ -1,15 +1,93 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %} {% block title %}
Success Danke
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<div class="container"> <div class="min-h-screen bg-gradient-to-b from-white to-gray-50 relative overflow-hidden">
<div class="row"> <header class="w-full bg-white/90 backdrop-blur-md shadow-lg fixed top-0 z-50 border-b border-gray-100">
<div class="col-md-12"> <div class="container mx-auto px-4 sm:px-6 lg:px-8 py-3 sm:py-4 flex justify-between items-center">
<h1 class="text-center">Success</h1> <a href="{{ path('app_home') }}" class="flex items-center space-x-2 group">
<p class="text-center">Your ticket has been successfully created.</p> <img src="{{ asset('images/logo.png') }}" alt="Logo" class="w-32 sm:w-36 md:w-40 h-auto group-hover:opacity-90 transition-opacity" />
</a>
<a href="{{ path('app_home') }}" class="bg-gradient-to-r from-red-500 to-orange-500 hover:from-red-600 hover:to-orange-600 text-white px-6 py-2.5 rounded-full text-sm font-medium shadow-md hover:shadow-lg transition-all duration-300 flex items-center space-x-2">
<twig:ux:icon name="line-md:home" class="w-4 h-4" />
<span>Zurück zur Startseite</span>
</a>
</div>
</header>
<div class="container mx-auto px-4 sm:px-6 lg:px-8 pt-24 sm:pt-28 md:pt-32 pb-16 sm:pb-20 flex flex-col items-center justify-center relative z-10">
<div class="w-full max-w-3xl mx-auto text-center">
<div class="mb-8">
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-green-100 mb-6">
<twig:ux:icon name="heroicons:check-circle" class="w-10 h-10 text-green-500" />
</div>
<h1 class="text-3xl sm:text-4xl font-bold text-gray-900 mb-4">Vielen Dank für deine Bestellung!</h1>
<p class="text-lg text-gray-600">Wir freuen uns, dich beim Abiball zu sehen.</p>
</div>
<div class="grid grid-cols-2 gap-5">
<div class="bg-white rounded-2xl shadow-lg p-8 border border-gray-100 col-1">
<h2 class="text-2xl font-semibold mb-6 w-full bg-clip-text text-transparent bg-gradient-to-r from-red-500 to-orange-500">Veranstaltungsdetails</h2>
<div class="max-w-xs mx-auto">
<div class="space-y-4">
<div class="flex justify-start space-x-3">
<twig:ux:icon name="heroicons:calendar" class="w-6 h-6 text-gray-400" />
<p class="text-gray-700">Sa. 28.06.2025</p>
</div>
<div class="flex justify-start space-x-3">
<twig:ux:icon name="heroicons:clock" class="w-6 h-6 text-gray-400" />
<p class="text-gray-700">17:30 - Open End</p>
</div>
<div class="flex justify-start space-x-3">
<twig:ux:icon name="heroicons:map-pin" class="w-6 h-6 text-gray-400" />
<p class="text-gray-700">Graubündener Str. 4, 28325 Bremen</p>
</div>
<div class="flex justify-start space-x-3">
<twig:ux:icon name="tabler:shirt" class="w-6 h-6 text-gray-400" />
<p class="text-gray-700">Abendkleidung</p>
</div>
</div>
</div>
</div>
<div class="bg-white rounded-2xl shadow-lg p-8 border border-gray-100 col-2">
<h2 class="text-2xl font-semibold mb-6 w-full bg-clip-text text-transparent bg-gradient-to-r from-red-500 to-orange-500">Ablauf</h2>
<div class="max-w-xs mx-auto">
<div class="space-y-4">
<div class="flex items-center gap-3">
<twig:ux:icon name="heroicons:ticket" class="w-6 h-6 text-gray-400 flex-shrink-0" />
<span class="text-gray-600 font-medium whitespace-nowrap">17:30-19:30</span>
<span class="text-gray-700">Einlass & Platznehmen</span>
</div>
<div class="flex items-center gap-3">
<twig:ux:icon name="heroicons:cake" class="w-6 h-6 text-gray-400 flex-shrink-0" />
<span class="text-gray-600 font-medium whitespace-nowrap">19:30-22:00</span>
<span class="text-gray-700">Dinner & Beisammensein</span>
</div>
<div class="flex items-center gap-3">
<twig:ux:icon name="heroicons:musical-note" class="w-6 h-6 text-gray-400 flex-shrink-0" />
<span class="text-gray-600 font-medium whitespace-nowrap">ab 22:00</span>
<span class="text-gray-700">Feier & Tanz im Saal</span>
</div>
</div>
</div>
</div>
</div>
<div class="mt-8 pt-6 border-t border-gray-200 text-center">
<div class="flex items-center justify-center">
<a href="{{ path('app_calendar') }}"
target="_blank"
class="inline-flex items-center px-6 py-3 bg-gray-200 hover:bg-gray-300 rounded transition-colors">
<twig:ux:icon name="heroicons:calendar" class="w-5 h-5 text-gray-600 mr-2" />
<span class="text-sm font-medium text-gray-700">Zum Kalender hinzufügen</span>
</a>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>