add success page #27
3
.ddev/commands/host/cf
Executable file
3
.ddev/commands/host/cf
Executable 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"
|
@ -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');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
50
src/Service/EventService.php
Normal file
50
src/Service/EventService.php
Normal 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'],
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user