added marketing page and moved events&general landing page
This commit is contained in:
207
app/Http/Controllers/MarketingController.php
Normal file
207
app/Http/Controllers/MarketingController.php
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Stripe\Stripe;
|
||||||
|
use Stripe\Checkout\Session;
|
||||||
|
use Stripe\StripeClient;
|
||||||
|
use Exception;
|
||||||
|
use PayPal\Api\Amount;
|
||||||
|
use PayPal\Api\Payer;
|
||||||
|
use PayPal\Api\Payment;
|
||||||
|
use PayPal\Api\RedirectUrls;
|
||||||
|
use PayPal\Api\Transaction;
|
||||||
|
use PayPal\Rest\ApiContext;
|
||||||
|
use PayPal\Auth\OAuthTokenCredential;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\Models\EventPurchase;
|
||||||
|
|
||||||
|
class MarketingController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
\Stripe\Stripe::setApiKey(config('services.stripe.key'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$packages = [
|
||||||
|
['id' => 'basic', 'name' => 'Basic', 'events' => 1, 'price' => 0, 'description' => '1 Event, 100 Fotos, Grundfunktionen'],
|
||||||
|
['id' => 'standard', 'name' => 'Standard', 'events' => 10, 'price' => 99, 'description' => '10 Events, Unbegrenzt Fotos, Erweiterte Features'],
|
||||||
|
['id' => 'premium', 'name' => 'Premium', 'events' => 50, 'price' => 199, 'description' => '50 Events, Support & Custom, Alle Features'],
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('marketing', compact('packages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contact(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'email' => 'required|email|max:255',
|
||||||
|
'message' => 'required|string|max:1000',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Mail::raw("Kontakt-Anfrage von {$request->name} ({$request->email}): {$request->message}", function ($message) use ($request) {
|
||||||
|
$message->to('admin@fotospiel.de')
|
||||||
|
->subject('Neue Kontakt-Anfrage');
|
||||||
|
});
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Nachricht gesendet!');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkout(Request $request, $package)
|
||||||
|
{
|
||||||
|
$packages = [
|
||||||
|
'basic' => ['name' => 'Basic', 'price' => 0, 'events' => 1],
|
||||||
|
'standard' => ['name' => 'Standard', 'price' => 9900, 'events' => 10], // cents
|
||||||
|
'premium' => ['name' => 'Premium', 'price' => 19900, 'events' => 50],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!isset($packages[$package])) {
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pkg = $packages[$package];
|
||||||
|
|
||||||
|
if ($pkg['price'] == 0) {
|
||||||
|
// Free package: create tenant and event
|
||||||
|
$tenant = Tenant::create([
|
||||||
|
'name' => $request->input('tenant_name', 'New Tenant'),
|
||||||
|
'slug' => Str::slug('new-' . now()),
|
||||||
|
'email' => $request->input('email'),
|
||||||
|
'events_remaining' => $pkg['events'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create initial event
|
||||||
|
$event = $tenant->events()->create([
|
||||||
|
'name' => $request->input('event_name', 'My Event'),
|
||||||
|
'slug' => Str::slug($request->input('event_name', 'my-event')),
|
||||||
|
'status' => 'active',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$purchase = EventPurchase::create([
|
||||||
|
'tenant_id' => $tenant->id,
|
||||||
|
'events_purchased' => $pkg['events'],
|
||||||
|
'amount' => 0,
|
||||||
|
'currency' => 'EUR',
|
||||||
|
'provider' => 'free',
|
||||||
|
'status' => 'completed',
|
||||||
|
'purchased_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect("/admin/tenants/{$tenant->id}/edit")->with('success', 'Konto erstellt! Willkommen bei Fotospiel.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$stripe = new \Stripe\StripeClient(config('services.stripe.secret'));
|
||||||
|
$session = $stripe->checkout->sessions->create([
|
||||||
|
'payment_method_types' => ['card'],
|
||||||
|
'line_items' => [[
|
||||||
|
'price_data' => [
|
||||||
|
'currency' => 'eur',
|
||||||
|
'product_data' => [
|
||||||
|
'name' => $pkg['name'] . ' Package',
|
||||||
|
],
|
||||||
|
'unit_amount' => $pkg['price'],
|
||||||
|
],
|
||||||
|
'quantity' => 1,
|
||||||
|
]],
|
||||||
|
'mode' => 'payment',
|
||||||
|
'success_url' => route('marketing.success', $package),
|
||||||
|
'cancel_url' => route('marketing'),
|
||||||
|
'metadata' => [
|
||||||
|
'package' => $package,
|
||||||
|
'events' => $pkg['events'],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect($session->url, 303);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stripeCheckout($sessionId)
|
||||||
|
{
|
||||||
|
// Handle Stripe success
|
||||||
|
return view('marketing.success', ['provider' => 'Stripe']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paypalCheckout(Request $request, $package)
|
||||||
|
{
|
||||||
|
$packages = [
|
||||||
|
'basic' => ['name' => 'Basic', 'price' => 0, 'events' => 1],
|
||||||
|
'standard' => ['name' => 'Standard', 'price' => 99, 'events' => 10],
|
||||||
|
'premium' => ['name' => 'Premium', 'price' => 199, 'events' => 50],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!isset($packages[$package])) {
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pkg = $packages[$package];
|
||||||
|
|
||||||
|
if ($pkg['price'] == 0) {
|
||||||
|
// Free package: create tenant and event
|
||||||
|
$tenant = Tenant::create([
|
||||||
|
'name' => $request->input('tenant_name', 'New Tenant'),
|
||||||
|
'slug' => Str::slug('new-' . now()),
|
||||||
|
'email' => $request->input('email'),
|
||||||
|
'events_remaining' => $pkg['events'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create initial event
|
||||||
|
$event = $tenant->events()->create([
|
||||||
|
'name' => $request->input('event_name', 'My Event'),
|
||||||
|
'slug' => Str::slug($request->input('event_name', 'my-event')),
|
||||||
|
'status' => 'active',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$purchase = EventPurchase::create([
|
||||||
|
'tenant_id' => $tenant->id,
|
||||||
|
'events_purchased' => $pkg['events'],
|
||||||
|
'amount' => 0,
|
||||||
|
'currency' => 'EUR',
|
||||||
|
'provider' => 'free',
|
||||||
|
'status' => 'completed',
|
||||||
|
'purchased_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect("/admin/tenants/{$tenant->id}/edit")->with('success', 'Konto erstellt! Willkommen bei Fotospiel.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$apiContext = new ApiContext(
|
||||||
|
new OAuthTokenCredential(
|
||||||
|
config('services.paypal.client_id'),
|
||||||
|
config('services.paypal.secret')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$payment = new Payment();
|
||||||
|
$payer = new Payer();
|
||||||
|
$payer->setPaymentMethod('paypal');
|
||||||
|
|
||||||
|
$amountObj = new Amount();
|
||||||
|
$amountObj->setCurrency('EUR');
|
||||||
|
$amountObj->setTotal($pkg['price']);
|
||||||
|
|
||||||
|
$transaction = new Transaction();
|
||||||
|
$transaction->setAmount($amountObj);
|
||||||
|
|
||||||
|
$redirectUrls = new RedirectUrls();
|
||||||
|
$redirectUrls->setReturnUrl(route('marketing.success', $package));
|
||||||
|
$redirectUrls->setCancelUrl(route('marketing'));
|
||||||
|
|
||||||
|
$payment->setIntent('sale')
|
||||||
|
->setPayer($payer)
|
||||||
|
->setTransactions([$transaction])
|
||||||
|
->setRedirectUrls($redirectUrls);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$payment->create($apiContext);
|
||||||
|
return redirect($payment->getApprovalLink());
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return back()->with('error', 'Zahlung fehlgeschlagen');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
app/Http/Controllers/PayPalWebhookController.php
Normal file
56
app/Http/Controllers/PayPalWebhookController.php
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use App\Models\EventPurchase;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
|
||||||
|
class PayPalWebhookController extends Controller
|
||||||
|
{
|
||||||
|
public function handle(Request $request)
|
||||||
|
{
|
||||||
|
$input = $request->all();
|
||||||
|
$ipnMessage = $input['ipn_track_id'] ?? null;
|
||||||
|
$payerEmail = $input['payer_email'] ?? null;
|
||||||
|
$paymentStatus = $input['payment_status'] ?? null;
|
||||||
|
$mcGross = $input['mc_gross'] ?? 0;
|
||||||
|
$packageId = $input['custom'] ?? null;
|
||||||
|
|
||||||
|
if ($paymentStatus === 'Completed' && $mcGross > 0) {
|
||||||
|
// Verify IPN with PayPal (simplified; use SDK for full verification)
|
||||||
|
// $verified = $this->verifyIPN($input);
|
||||||
|
|
||||||
|
// Find or create tenant (for public checkout, perhaps create new or use session)
|
||||||
|
// For now, assume tenant_id from custom or session
|
||||||
|
$tenantId = $packageId ? Tenant::where('slug', $packageId)->first()->id ?? 1 : 1;
|
||||||
|
|
||||||
|
// Create purchase and increment credits
|
||||||
|
$purchase = EventPurchase::create([
|
||||||
|
'tenant_id' => $tenantId, // Implement tenant resolution
|
||||||
|
'events_purchased' => $mcGross / 49, // Example: 49€ per event credit
|
||||||
|
'amount' => $mcGross,
|
||||||
|
'currency' => $input['mc_currency'] ?? 'EUR',
|
||||||
|
'provider' => 'paypal',
|
||||||
|
'external_receipt_id' => $ipnMessage,
|
||||||
|
'status' => 'completed',
|
||||||
|
'purchased_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tenant = Tenant::find($tenantId);
|
||||||
|
$tenant->incrementCredits($purchase->events_purchased, 'paypal_purchase', 'PayPal IPN', $purchase->id);
|
||||||
|
|
||||||
|
Log::info('PayPal IPN processed', $input);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response('OK', 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function verifyIPN($input)
|
||||||
|
{
|
||||||
|
// Use PayPal SDK to verify
|
||||||
|
// Return true/false
|
||||||
|
return true; // Placeholder
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"laravel/sanctum": "^4.2",
|
"laravel/sanctum": "^4.2",
|
||||||
"laravel/tinker": "^2.10.1",
|
"laravel/tinker": "^2.10.1",
|
||||||
"laravel/wayfinder": "^0.1.9",
|
"laravel/wayfinder": "^0.1.9",
|
||||||
|
"paypal/rest-api-sdk-php": "^1.6",
|
||||||
"simplesoftwareio/simple-qrcode": "^4.2",
|
"simplesoftwareio/simple-qrcode": "^4.2",
|
||||||
"stripe/stripe-php": "^17.6"
|
"stripe/stripe-php": "^17.6"
|
||||||
},
|
},
|
||||||
|
|||||||
55
composer.lock
generated
55
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "855707cf018e10451bf627dbd6593f0a",
|
"content-hash": "b0eebcaa1d38fb0a00e546fad7bdc991",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "anourvalar/eloquent-serialize",
|
"name": "anourvalar/eloquent-serialize",
|
||||||
@@ -4407,6 +4407,59 @@
|
|||||||
},
|
},
|
||||||
"time": "2024-05-08T12:36:18+00:00"
|
"time": "2024-05-08T12:36:18+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "paypal/rest-api-sdk-php",
|
||||||
|
"version": "v1.6.4",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/paypal/PayPal-PHP-SDK.git",
|
||||||
|
"reference": "06837d290c4906578cfd92786412dff330a1429c"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/paypal/PayPal-PHP-SDK/zipball/06837d290c4906578cfd92786412dff330a1429c",
|
||||||
|
"reference": "06837d290c4906578cfd92786412dff330a1429c",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-json": "*",
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "3.7.*"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"PayPal": "lib/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache2"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PayPal",
|
||||||
|
"homepage": "https://github.com/paypal/rest-api-sdk-php/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PayPal's PHP SDK for REST APIs",
|
||||||
|
"homepage": "http://paypal.github.io/PayPal-PHP-SDK/",
|
||||||
|
"keywords": [
|
||||||
|
"payments",
|
||||||
|
"paypal",
|
||||||
|
"rest",
|
||||||
|
"sdk"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/paypal/PayPal-PHP-SDK/issues",
|
||||||
|
"source": "https://github.com/paypal/PayPal-PHP-SDK/tree/stable"
|
||||||
|
},
|
||||||
|
"abandoned": "paypal/paypal-server-sdk",
|
||||||
|
"time": "2016-01-20T17:45:52+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phpoption/phpoption",
|
"name": "phpoption/phpoption",
|
||||||
"version": "1.9.4",
|
"version": "1.9.4",
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ function HomeLayout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const router = createBrowserRouter([
|
export const router = createBrowserRouter([
|
||||||
{ path: '/', element: <SimpleLayout title="Fotospiel"><LandingPage /></SimpleLayout> },
|
{ path: '/event', element: <SimpleLayout title="Event"><LandingPage /></SimpleLayout> },
|
||||||
{
|
{
|
||||||
path: '/setup/:slug',
|
path: '/setup/:slug',
|
||||||
element: <SetupLayout />,
|
element: <SetupLayout />,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import AppLogoIcon from '@/components/app-logo-icon';
|
import AppLogoIcon from '@/components/app-logo-icon';
|
||||||
import { home } from '@/routes';
|
import { marketing } from '@/routes';
|
||||||
import { Link } from '@inertiajs/react';
|
import { Link } from '@inertiajs/react';
|
||||||
import { type PropsWithChildren } from 'react';
|
import { type PropsWithChildren } from 'react';
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ export default function AuthSimpleLayout({ children, title, description }: Props
|
|||||||
<div className="w-full max-w-sm">
|
<div className="w-full max-w-sm">
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-8">
|
||||||
<div className="flex flex-col items-center gap-4">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<Link href={home()} className="flex flex-col items-center gap-2 font-medium">
|
<Link href={marketing()} className="flex flex-col items-center gap-2 font-medium">
|
||||||
<div className="mb-1 flex h-9 w-9 items-center justify-center rounded-md">
|
<div className="mb-1 flex h-9 w-9 items-center justify-center rounded-md">
|
||||||
<AppLogoIcon className="size-9 fill-current text-[var(--foreground)] dark:text-white" />
|
<AppLogoIcon className="size-9 fill-current text-[var(--foreground)] dark:text-white" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<title>{{ config('app.name', 'Fotospiel') }}</title>
|
<title>{{ config('app.name', 'Fotospiel') }}</title>
|
||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
@viteReactRefresh
|
@viteReactRefresh
|
||||||
@vite('resources/js/guest/main.tsx')
|
@vite(['resources/css/app.css', 'resources/js/guest/main.tsx'])
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
16
resources/views/legal/datenschutz.blade.php
Normal file
16
resources/views/legal/datenschutz.blade.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Datenschutzerklärung - Fotospiel</title>
|
||||||
|
</head>
|
||||||
|
<body class="container mx-auto px-4 py-8">
|
||||||
|
<h1>Datenschutzerklärung</h1>
|
||||||
|
<p>Wir nehmen den Schutz Ihrer persönlichen Daten sehr ernst und halten uns strikt an die Regeln der Datenschutzgesetze.</p>
|
||||||
|
<p>Verantwortlich: Fotospiel GmbH, Musterstraße 1, 12345 Musterstadt</p>
|
||||||
|
<p>Datenerfassung: Keine PII-Speicherung, anonyme Sessions für Gäste. E-Mails werden nur für Kontaktzwecke verarbeitet.</p>
|
||||||
|
<p>Ihre Rechte: Auskunft, Löschung, Widerspruch. Kontaktieren Sie uns unter <a href="/kontakt">Kontakt</a>.</p>
|
||||||
|
<p>Cookies: Nur funktionale Cookies für die PWA.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
21
resources/views/legal/impressum.blade.php
Normal file
21
resources/views/legal/impressum.blade.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Impressum - Fotospiel</title>
|
||||||
|
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
|
||||||
|
</head>
|
||||||
|
<body class="container mx-auto px-4 py-8">
|
||||||
|
<h1 class="text-2xl font-bold mb-4">Impressum</h1>
|
||||||
|
<p class="mb-4">Angaben gemäß § 5 TMG</p>
|
||||||
|
<p class="mb-4">Fotospiel GmbH<br>
|
||||||
|
Musterstraße 1<br>
|
||||||
|
12345 Musterstadt<br>
|
||||||
|
Vertreten durch: Max Mustermann<br>
|
||||||
|
Kontakt: <a href="/kontakt">Kontakt</a></p>
|
||||||
|
<p class="mb-4">Umsatzsteuer-ID: DE123456789</p>
|
||||||
|
<p>Registergericht: Amtsgericht Musterstadt</p>
|
||||||
|
<p>Handelsregister: HRB 12345</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
205
resources/views/marketing.blade.php
Normal file
205
resources/views/marketing.blade.php
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Fotospiel - Event-Fotos einfach und sicher</title>
|
||||||
|
<meta name="description" content="Erstellen Sie unvergessliche Event-Fotos mit unserer PWA-Plattform. Für Hochzeiten, Firmenevents und mehr. Kostenloser Einstieg.">
|
||||||
|
<link rel="icon" href="{{ asset('logo.svg') }}" type="image/svg+xml">
|
||||||
|
@vite(['resources/css/app.css'])
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-50 text-gray-900">
|
||||||
|
<!-- Hero Section -->
|
||||||
|
<section class="bg-gradient-to-r from-[#FFB6C1] via-[#FFD700] to-[#87CEEB] text-white py-20 px-4">
|
||||||
|
<div class="container mx-auto text-center">
|
||||||
|
<h1 class="text-4xl md:text-6xl font-bold mb-4">Fotospiel</h1>
|
||||||
|
<p class="text-xl md:text-2xl mb-8 max-w-3xl mx-auto">Erleben Sie Events durch professionelle Fotos. Unsere sichere PWA-Plattform für Gäste und Organisatoren. Einfach, mobil und datenschutzkonform.</p>
|
||||||
|
<a href="/buy-credits/basic" class="bg-white text-[#FFB6C1] px-8 py-4 rounded-full font-semibold text-lg hover:bg-gray-100 transition">Jetzt starten – Kostenlos</a>
|
||||||
|
<img src="https://images.unsplash.com/photo-1511285560929-80b456fea0bc?w=800&h=400&fit=crop" alt="Event-Fotos" class="mt-8 mx-auto rounded-lg shadow-lg max-w-4xl">
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Features Section -->
|
||||||
|
<section class="py-20 px-4 bg-white">
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-12">Warum Fotospiel?</h2>
|
||||||
|
<div class="grid md:grid-cols-3 gap-8">
|
||||||
|
<div class="text-center p-6">
|
||||||
|
<div class="w-16 h-16 bg-[#FFB6C1] rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-semibold mb-2">Sichere Uploads</h3>
|
||||||
|
<p>GDPR-konform, anonyme Sessions, keine PII-Speicherung.</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-6">
|
||||||
|
<div class="w-16 h-16 bg-[#FFD700] rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path></svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-semibold mb-2">Mobile PWA</h3>
|
||||||
|
<p>Offline-fähig, App-ähnlich, für iOS und Android.</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-6">
|
||||||
|
<div class="w-16 h-16 bg-[#87CEEB] rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-semibold mb-2">Schnell & Einfach</h3>
|
||||||
|
<p>Automatische Thumbnails, Echtzeit-Updates, einfache Bedienung.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 4-Schritte Section -->
|
||||||
|
<section class="py-20 px-4 bg-gray-50">
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-12">So funktioniert es – in 4 einfachen Schritten</h2>
|
||||||
|
<div class="grid md:grid-cols-4 gap-8">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="w-12 h-12 bg-[#FFB6C1] rounded-full flex items-center justify-center mx-auto mb-4 text-white font-bold">1</div>
|
||||||
|
<h3 class="font-semibold mb-2">Event erstellen</h3>
|
||||||
|
<p>Als Organisator: Registrieren, Event anlegen, Gäste einladen.</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="w-12 h-12 bg-[#FFD700] rounded-full flex items-center justify-center mx-auto mb-4 text-white font-bold">2</div>
|
||||||
|
<h3 class="font-semibold mb-2">Fotos hochladen</h3>
|
||||||
|
<p>Gäste: PWA öffnen, Fotos via Kamera oder Galerie teilen.</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="w-12 h-12 bg-[#87CEEB] rounded-full flex items-center justify-center mx-auto mb-4 text-white font-bold">3</div>
|
||||||
|
<h3 class="font-semibold mb-2">Freigaben & Likes</h3>
|
||||||
|
<p>Emotions auswählen, Fotos liken, Galerie browsen.</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="w-12 h-12 bg-[#FFB6C1] rounded-full flex items-center justify-center mx-auto mb-4 text-white font-bold">4</div>
|
||||||
|
<h3 class="font-semibold mb-2">Download & Teilen</h3>
|
||||||
|
<p>Freigegebene Fotos herunterladen, Event abschließen.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Preise Section -->
|
||||||
|
<section class="py-20 px-4 bg-white">
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-12">Tarife</h2>
|
||||||
|
<div class="grid md:grid-cols-3 gap-8 max-w-4xl mx-auto">
|
||||||
|
<div class="bg-gray-50 p-8 rounded-lg text-center border-2 border-gray-200">
|
||||||
|
<h3 class="text-2xl font-bold mb-4">Basic</h3>
|
||||||
|
<p class="text-4xl font-bold text-[#FFB6C1] mb-4">0 €</p>
|
||||||
|
<ul class="mb-6 space-y-2">
|
||||||
|
<li>1 Event</li>
|
||||||
|
<li>100 Fotos</li>
|
||||||
|
<li>Grundfunktionen</li>
|
||||||
|
</ul>
|
||||||
|
<a href="/buy-credits/basic" class="bg-[#FFB6C1] text-white px-6 py-3 rounded-full font-semibold">Kostenlos starten</a>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 p-8 rounded-lg text-center border-2 border-[#FFD700]">
|
||||||
|
<h3 class="text-2xl font-bold mb-4">Standard</h3>
|
||||||
|
<p class="text-4xl font-bold text-[#FFD700] mb-4">99 €</p>
|
||||||
|
<ul class="mb-6 space-y-2">
|
||||||
|
<li>10 Events</li>
|
||||||
|
<li>Unbegrenzt Fotos</li>
|
||||||
|
<li>Erweiterte Features</li>
|
||||||
|
</ul>
|
||||||
|
<a href="/buy-credits/standard" class="bg-[#FFD700] text-white px-6 py-3 rounded-full font-semibold">Kaufen</a>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 p-8 rounded-lg text-center border-2 border-gray-200">
|
||||||
|
<h3 class="text-2xl font-bold mb-4">Premium</h3>
|
||||||
|
<p class="text-4xl font-bold text-[#87CEEB] mb-4">199 €</p>
|
||||||
|
<ul class="mb-6 space-y-2">
|
||||||
|
<li>50 Events</li>
|
||||||
|
<li>Support & Custom</li>
|
||||||
|
<li>Alle Features</li>
|
||||||
|
</ul>
|
||||||
|
<a href="/buy-credits/premium" class="bg-[#87CEEB] text-white px-6 py-3 rounded-full font-semibold">Kaufen</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Kontakt Section -->
|
||||||
|
<section class="py-20 px-4 bg-gray-50">
|
||||||
|
<div class="container mx-auto max-w-2xl">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-12">Kontakt</h2>
|
||||||
|
<form method="POST" action="/kontakt" class="space-y-4">
|
||||||
|
@csrf
|
||||||
|
<div>
|
||||||
|
<label for="name" class="block text-sm font-medium mb-2">Name</label>
|
||||||
|
<input type="text" id="name" name="name" required class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#FFB6C1]">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="email" class="block text-sm font-medium mb-2">E-Mail</label>
|
||||||
|
<input type="email" id="email" name="email" required class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#FFB6C1]">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="message" class="block text-sm font-medium mb-2">Nachricht</label>
|
||||||
|
<textarea id="message" name="message" rows="4" required class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#FFB6C1]"></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="w-full bg-[#FFB6C1] text-white py-3 rounded-md font-semibold hover:bg-[#FF69B4] transition">Senden</button>
|
||||||
|
</form>
|
||||||
|
@if (session('success'))
|
||||||
|
<p class="mt-4 text-green-600 text-center">{{ session('success') }}</p>
|
||||||
|
@endif
|
||||||
|
@if ($errors->any())
|
||||||
|
<div class="mt-4 p-4 bg-red-100 border border-red-400 rounded-md">
|
||||||
|
<ul class="list-disc list-inside">
|
||||||
|
@foreach ($errors->all() as $error)
|
||||||
|
<li>{{ $error }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Testimonials Section -->
|
||||||
|
<section class="py-20 px-4 bg-white">
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-12">Was unsere Kunden sagen</h2>
|
||||||
|
<div class="grid md:grid-cols-2 gap-8 max-w-4xl mx-auto">
|
||||||
|
<div class="bg-gray-50 p-6 rounded-lg">
|
||||||
|
<p class="mb-4">"Perfekt für unsere Hochzeit! Einfach und sicher."</p>
|
||||||
|
<p class="font-semibold">- Anna & Max</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 p-6 rounded-lg">
|
||||||
|
<p class="mb-4">"Großes Firmenevent – alle Fotos zentral und mobil."</p>
|
||||||
|
<p class="font-semibold">- Team XYZ GmbH</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- FAQ Section -->
|
||||||
|
<section class="py-20 px-4 bg-gray-50">
|
||||||
|
<div class="container mx-auto max-w-3xl">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-12">Häufige Fragen</h2>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="bg-white p-4 rounded-lg">
|
||||||
|
<h3 class="font-semibold">Ist es kostenlos?</h3>
|
||||||
|
<p>Ja, der Basic-Tarif ist kostenlos für 1 Event. Upgrades ab 99€.</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white p-4 rounded-lg">
|
||||||
|
<h3 class="font-semibold">Datenschutz?</h3>
|
||||||
|
<p>100% GDPR-konform. Keine personenbezogenen Daten gespeichert. Siehe <a href="/datenschutz" class="text-[#FFB6C1]">Datenschutzerklärung</a>.</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white p-4 rounded-lg">
|
||||||
|
<h3 class="font-semibold">Wie lade ich Fotos hoch?</h3>
|
||||||
|
<p>Über die PWA-App: Kamera oder Galerie, Emotions zuweisen, teilen.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-gray-800 text-white py-8 px-4">
|
||||||
|
<div class="container mx-auto text-center">
|
||||||
|
<p>© 2025 Fotospiel GmbH. Alle Rechte vorbehalten.</p>
|
||||||
|
<div class="mt-4 space-x-4">
|
||||||
|
<a href="/impressum" class="hover:text-[#FFB6C1]">Impressum</a>
|
||||||
|
<a href="/datenschutz" class="hover:text-[#FFB6C1]">Datenschutz</a>
|
||||||
|
<a href="/kontakt" class="hover:text-[#FFB6C1]">Kontakt</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13
resources/views/marketing/success.blade.php
Normal file
13
resources/views/marketing/success.blade.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Zahlung erfolgreich - Fotospiel</title>
|
||||||
|
</head>
|
||||||
|
<body class="container mx-auto px-4 py-8 text-center">
|
||||||
|
<h1>Zahlung erfolgreich!</h1>
|
||||||
|
<p>Vielen Dank für Ihren Kauf. Ihr Konto wurde aktualisiert.</p>
|
||||||
|
<a href="/admin" class="bg-green-600 text-white px-4 py-2 rounded">Zum Admin-Dashboard</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
|
|
||||||
// Public landing: serve Guest PWA shell at root
|
// Marketing-Seite unter Root
|
||||||
Route::view('/', 'guest')->name('home');
|
Route::view('/', 'marketing')->name('marketing');
|
||||||
|
|
||||||
Route::middleware(['auth', 'verified'])->group(function () {
|
Route::middleware(['auth', 'verified'])->group(function () {
|
||||||
Route::get('dashboard', function () {
|
Route::get('dashboard', function () {
|
||||||
@@ -15,19 +15,32 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
|||||||
require __DIR__.'/settings.php';
|
require __DIR__.'/settings.php';
|
||||||
require __DIR__.'/auth.php';
|
require __DIR__.'/auth.php';
|
||||||
|
|
||||||
// Guest PWA shell (served for event and /pwa paths; React handles routing)
|
// Guest PWA shell for /event and sub-routes
|
||||||
|
Route::view('/event/{any?}', 'guest')->where('any', '.*');
|
||||||
Route::view('/e/{any?}', 'guest')->where('any', '.*');
|
Route::view('/e/{any?}', 'guest')->where('any', '.*');
|
||||||
Route::view('/pwa/{any?}', 'guest')->where('any', '.*');
|
Route::view('/pwa/{any?}', 'guest')->where('any', '.*');
|
||||||
Route::view('/legal/{any?}', 'guest')->where('any', '.*');
|
|
||||||
|
|
||||||
// Minimal public API for Guest PWA (stateless; no CSRF)
|
// Minimal public API for Guest PWA (stateless; no CSRF)
|
||||||
Route::prefix('api/v1')->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class])->group(function () {
|
Route::prefix('api/v1')->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class])->group(function () {
|
||||||
// Public legal pages
|
// Public legal pages (for marketing)
|
||||||
Route::get('/legal/{slug}', [\App\Http\Controllers\Api\LegalController::class, 'show']);
|
Route::get('/impressum', function () {
|
||||||
|
return view('legal.impressum');
|
||||||
|
})->name('impressum');
|
||||||
|
Route::get('/datenschutz', function () {
|
||||||
|
return view('legal.datenschutz');
|
||||||
|
})->name('datenschutz');
|
||||||
|
Route::get('/kontakt', function () {
|
||||||
|
return view('legal.kontakt');
|
||||||
|
})->name('kontakt');
|
||||||
|
Route::post('/kontakt', [\App\Http\Controllers\MarketingController::class, 'contact'])->name('kontakt.submit');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Stripe webhooks (no CSRF, no auth)
|
// Stripe webhooks (no CSRF, no auth)
|
||||||
Route::post('/webhooks/stripe', [\App\Http\Controllers\StripeWebhookController::class, 'handle']);
|
Route::post('/webhooks/stripe', [\App\Http\Controllers\StripeWebhookController::class, 'handle']);
|
||||||
|
// PayPal IPN webhook
|
||||||
|
Route::post('/webhooks/paypal', [\App\Http\Controllers\PayPalWebhookController::class, 'handle']);
|
||||||
|
// PayPal IPN webhook
|
||||||
|
Route::post('/webhooks/paypal', [\App\Http\Controllers\PayPalWebhookController::class, 'handle']);
|
||||||
|
|
||||||
// CSV templates for Super Admin imports
|
// CSV templates for Super Admin imports
|
||||||
Route::get('/super-admin/templates/emotions.csv', function () {
|
Route::get('/super-admin/templates/emotions.csv', function () {
|
||||||
@@ -60,3 +73,14 @@ Route::get('/super-admin/templates/tasks.csv', function () {
|
|||||||
};
|
};
|
||||||
return response()->stream($callback, 200, $headers);
|
return response()->stream($callback, 200, $headers);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// E-Commerce Routen für Marketing
|
||||||
|
Route::get('/buy-credits/{package}', [\App\Http\Controllers\MarketingController::class, 'checkout'])->name('buy.credits');
|
||||||
|
Route::get('/checkout/{sessionId}', [\App\Http\Controllers\MarketingController::class, 'stripeCheckout']);
|
||||||
|
Route::get('/paypal-checkout/{package}', [\App\Http\Controllers\MarketingController::class, 'paypalCheckout']);
|
||||||
|
Route::get('/marketing/success/{package}', [\App\Http\Controllers\MarketingController::class, 'success'])->name('marketing.success');
|
||||||
|
|
||||||
|
// E-Commerce Routen für Marketing
|
||||||
|
Route::get('/buy-credits/{package}', [\App\Http\Controllers\MarketingController::class, 'checkout'])->name('buy.credits');
|
||||||
|
Route::get('/checkout/{sessionId}', [\App\Http\Controllers\MarketingController::class, 'stripeCheckout']);
|
||||||
|
Route::get('/paypal-checkout/{package}', [\App\Http\Controllers\MarketingController::class, 'paypalCheckout']);
|
||||||
|
|||||||
Reference in New Issue
Block a user