integration vom Blog-plugin, hübschere webseite,
This commit is contained in:
@@ -292,7 +292,7 @@ class EventPublicController extends BaseController
|
||||
}
|
||||
|
||||
if ($since) {
|
||||
$query->where('created_at', '>', $since);
|
||||
$query->where('photos.created_at', '>', $since);
|
||||
}
|
||||
$locale = request()->query('locale', 'de');
|
||||
|
||||
|
||||
@@ -127,6 +127,34 @@ class MarketingController extends Controller
|
||||
return view('marketing.success', ['provider' => 'Stripe']);
|
||||
}
|
||||
|
||||
public function blogIndex(Request $request)
|
||||
{
|
||||
$locale = $request->get('locale', app()->getLocale());
|
||||
$posts = \Stephenjude\FilamentBlog\Models\Post::query()
|
||||
->where('is_published', true)
|
||||
->whereNotNull('published_at')
|
||||
->where('published_at', '<=', now())
|
||||
->whereJsonContains("translations->locale->title->{$locale}", true)
|
||||
->orderBy('published_at', 'desc')
|
||||
->paginate(8);
|
||||
|
||||
return view('marketing.blog', compact('posts'));
|
||||
}
|
||||
|
||||
public function blogShow($slug)
|
||||
{
|
||||
$locale = app()->getLocale();
|
||||
$post = \Stephenjude\FilamentBlog\Models\Post::query()
|
||||
->where('slug', $slug)
|
||||
->where('is_published', true)
|
||||
->whereNotNull('published_at')
|
||||
->where('published_at', '<=', now())
|
||||
->whereJsonContains("translations->locale->title->{$locale}", true)
|
||||
->firstOrFail();
|
||||
|
||||
return view('marketing.blog-show', compact('post'));
|
||||
}
|
||||
|
||||
public function paypalCheckout(Request $request, $package)
|
||||
{
|
||||
$packages = [
|
||||
|
||||
32
app/Http/Middleware/SetLocale.php
Normal file
32
app/Http/Middleware/SetLocale.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class SetLocale
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$locale = $request->segment(1);
|
||||
|
||||
$supportedLocales = ['de', 'en'];
|
||||
|
||||
if (in_array($locale, $supportedLocales)) {
|
||||
app()->setLocale($locale);
|
||||
session()->put('locale', $locale);
|
||||
} else {
|
||||
$locale = session('locale', config('app.locale', 'de'));
|
||||
app()->setLocale($locale);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
37
app/Http/Middleware/SuperAdminAuth.php
Normal file
37
app/Http/Middleware/SuperAdminAuth.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class SuperAdminAuth
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if ($request->is('super-admin/login') || $request->is('super-admin/register')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if (!Auth::check()) {
|
||||
abort(403, 'Nicht angemeldet.');
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
Log::info('SuperAdminAuth: User ID ' . $user->id . ', role: ' . $user->role);
|
||||
|
||||
if ($user->role !== 'super_admin') {
|
||||
abort(403, 'Zugriff nur für SuperAdmin. User ID: ' . $user->id . ', Role: ' . $user->role);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
25
app/Models/BlogCategory.php
Normal file
25
app/Models/BlogCategory.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Spatie\Translatable\HasTranslations;
|
||||
use Stephenjude\FilamentBlog\Models\Category as BaseCategory;
|
||||
|
||||
class BlogCategory extends BaseCategory
|
||||
{
|
||||
use HasFactory, SoftDeletes, HasTranslations;
|
||||
|
||||
protected $translatable = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'slug',
|
||||
'is_visible',
|
||||
'translations',
|
||||
];
|
||||
}
|
||||
32
app/Models/BlogPost.php
Normal file
32
app/Models/BlogPost.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Spatie\Translatable\HasTranslations;
|
||||
use Stephenjude\FilamentBlog\Models\Post as BasePost;
|
||||
|
||||
class BlogPost extends BasePost
|
||||
{
|
||||
use HasFactory, SoftDeletes, HasTranslations;
|
||||
|
||||
protected $translatable = [
|
||||
'title',
|
||||
'excerpt',
|
||||
'content',
|
||||
'meta_title',
|
||||
'meta_description',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'blog_author_id',
|
||||
'blog_category_id',
|
||||
'slug',
|
||||
'banner',
|
||||
'published_at',
|
||||
'is_published',
|
||||
'translations',
|
||||
];
|
||||
}
|
||||
23
app/Models/BlogTag.php
Normal file
23
app/Models/BlogTag.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Spatie\Translatable\HasTranslations;
|
||||
use Stephenjude\FilamentBlog\Models\Tag as BaseTag;
|
||||
|
||||
class BlogTag extends BaseTag
|
||||
{
|
||||
use HasFactory, SoftDeletes, HasTranslations;
|
||||
|
||||
protected $translatable = [
|
||||
'name',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'slug',
|
||||
'translations',
|
||||
];
|
||||
}
|
||||
@@ -35,5 +35,9 @@ class AppServiceProvider extends ServiceProvider
|
||||
RateLimiter::for('oauth', function (Request $request) {
|
||||
return Limit::perMinute(10)->by('oauth:' . ($request->ip() ?? 'unknown'));
|
||||
});
|
||||
|
||||
if ($this->app->runningInConsole()) {
|
||||
$this->app->register(\App\Providers\Filament\AdminPanelProvider::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
69
app/Providers/Filament/AdminPanelProvider.php
Normal file
69
app/Providers/Filament/AdminPanelProvider.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers\Filament;
|
||||
|
||||
use Filament\Http\Middleware\Authenticate;
|
||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||
use Filament\Pages;
|
||||
use Filament\Panel;
|
||||
use Filament\PanelProvider;
|
||||
use Filament\Support\Colors\Color;
|
||||
use Filament\Widgets;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
use Stephenjude\FilamentBlog\Filament\Resources\CategoryResource;
|
||||
use Stephenjude\FilamentBlog\Filament\Resources\PostResource;
|
||||
use Stephenjude\FilamentBlog\Filament\Resources\TagResource;
|
||||
use App\Models\BlogCategory;
|
||||
use App\Models\BlogPost;
|
||||
use App\Models\BlogTag;
|
||||
|
||||
class AdminPanelProvider extends PanelProvider
|
||||
{
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
->default()
|
||||
->id('admin')
|
||||
->path('admin')
|
||||
->login()
|
||||
->colors([
|
||||
'primary' => Color::Pink,
|
||||
])
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
|
||||
->pages([
|
||||
Pages\Dashboard::class,
|
||||
])
|
||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
||||
->widgets([
|
||||
Widgets\AccountWidget::class,
|
||||
Widgets\FilamentInfoWidget::class,
|
||||
])
|
||||
->middleware([
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartSession::class,
|
||||
AuthenticateSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
DisableBladeIconComponents::class,
|
||||
DispatchServingFilamentEvent::class,
|
||||
])
|
||||
->authMiddleware([
|
||||
Authenticate::class,
|
||||
])
|
||||
->resources([
|
||||
// Blog-Resources moved to SuperAdminPanel
|
||||
])
|
||||
// Remove blog models as they are global and handled in SuperAdmin
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -3,21 +3,33 @@
|
||||
namespace App\Providers\Filament;
|
||||
|
||||
use Filament\Http\Middleware\Authenticate;
|
||||
use Filament\Http\Middleware\AuthenticateSession;
|
||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||
use Filament\Pages\Dashboard;
|
||||
use Filament\Pages;
|
||||
use Filament\Panel;
|
||||
use Filament\PanelProvider;
|
||||
use Filament\Support\Colors\Color;
|
||||
use Filament\Widgets\AccountWidget;
|
||||
use Filament\Widgets\FilamentInfoWidget;
|
||||
use Filament\Widgets;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
use App\Filament\Resources\LegalPageResource;
|
||||
use App\Filament\Resources\TenantResource;
|
||||
use App\Filament\Pages\SuperAdminProfile;
|
||||
use App\Models\User;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\BlogPost;
|
||||
use App\Models\BlogCategory;
|
||||
use App\Models\BlogTag;
|
||||
use App\Filament\Widgets\PlatformStatsWidget;
|
||||
use App\Filament\Widgets\TopTenantsByUploads;
|
||||
use Stephenjude\FilamentBlog\Filament\Resources\CategoryResource;
|
||||
use Stephenjude\FilamentBlog\Filament\Resources\PostResource;
|
||||
use Stephenjude\FilamentBlog\BlogPlugin;
|
||||
|
||||
class SuperAdminPanelProvider extends PanelProvider
|
||||
{
|
||||
@@ -25,26 +37,27 @@ class SuperAdminPanelProvider extends PanelProvider
|
||||
{
|
||||
return $panel
|
||||
->default()
|
||||
->id('super-admin')
|
||||
->id('superadmin')
|
||||
->path('super-admin')
|
||||
->profile(\App\Filament\Pages\SuperAdminProfile::class, true)
|
||||
->login()
|
||||
->colors([
|
||||
'primary' => Color::Amber,
|
||||
'primary' => Color::Pink,
|
||||
])
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages')
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
|
||||
->pages([
|
||||
Dashboard::class,
|
||||
Pages\Dashboard::class,
|
||||
])
|
||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\Filament\Widgets')
|
||||
->plugin(
|
||||
BlogPlugin::make()
|
||||
)
|
||||
->profile(SuperAdminProfile::class)
|
||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
||||
->widgets([
|
||||
AccountWidget::class,
|
||||
FilamentInfoWidget::class,
|
||||
\App\Filament\Widgets\PlatformStatsWidget::class,
|
||||
\App\Filament\Widgets\UploadsPerDayChart::class,
|
||||
\App\Filament\Widgets\RecentPhotosTable::class,
|
||||
\App\Filament\Widgets\TopTenantsByUploads::class,
|
||||
\App\Filament\Widgets\EventsActiveToday::class,
|
||||
Widgets\AccountWidget::class,
|
||||
Widgets\FilamentInfoWidget::class,
|
||||
PlatformStatsWidget::class,
|
||||
TopTenantsByUploads::class,
|
||||
])
|
||||
->middleware([
|
||||
EncryptCookies::class,
|
||||
@@ -59,6 +72,18 @@ class SuperAdminPanelProvider extends PanelProvider
|
||||
])
|
||||
->authMiddleware([
|
||||
Authenticate::class,
|
||||
]);
|
||||
])
|
||||
->resources([
|
||||
TenantResource::class,
|
||||
LegalPageResource::class,
|
||||
])
|
||||
->authMiddleware([
|
||||
Authenticate::class,
|
||||
'superadmin.auth',
|
||||
])
|
||||
->authGuard('web')
|
||||
// SuperAdmin-Zugriff durch custom Middleware, globale Sichtbarkeit ohne Tenant-Isolation
|
||||
// Blog-Resources werden durch das Plugin-ServiceProvider automatisch registriert
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,14 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
'tenant.token' => TenantTokenGuard::class,
|
||||
'tenant.isolation' => TenantIsolation::class,
|
||||
'credit.check' => CreditCheckMiddleware::class,
|
||||
'locale' => \App\Http\Middleware\SetLocale::class,
|
||||
'superadmin.auth' => \App\Http\Middleware\SuperAdminAuth::class,
|
||||
]);
|
||||
|
||||
$middleware->encryptCookies(except: ['appearance', 'sidebar_state']);
|
||||
|
||||
$middleware->web(append: [
|
||||
\App\Http\Middleware\SetLocale::class,
|
||||
SetLocaleFromUser::class,
|
||||
HandleAppearance::class,
|
||||
HandleInertiaRequests::class,
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
Stephenjude\FilamentBlog\FilamentBlogServiceProvider::class,
|
||||
App\Providers\Filament\SuperAdminPanelProvider::class,
|
||||
];
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
"laravel/wayfinder": "^0.1.9",
|
||||
"paypal/rest-api-sdk-php": "^1.6",
|
||||
"simplesoftwareio/simple-qrcode": "^4.2",
|
||||
"spatie/laravel-translatable": "^6.11",
|
||||
"stephenjude/filament-blog": "*",
|
||||
"stripe/stripe-php": "^17.6"
|
||||
},
|
||||
"require-dev": {
|
||||
|
||||
339
composer.lock
generated
339
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "b0eebcaa1d38fb0a00e546fad7bdc991",
|
||||
"content-hash": "a79b02d59d8ee7716beea2fc8442a905",
|
||||
"packages": [
|
||||
{
|
||||
"name": "anourvalar/eloquent-serialize",
|
||||
@@ -1386,6 +1386,43 @@
|
||||
},
|
||||
"time": "2025-09-04T14:12:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/spatie-laravel-tags-plugin",
|
||||
"version": "v3.3.30",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/spatie-laravel-tags-plugin.git",
|
||||
"reference": "7763c2bab92c619cdd9294c69004f89e136c0afc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/spatie-laravel-tags-plugin/zipball/7763c2bab92c619cdd9294c69004f89e136c0afc",
|
||||
"reference": "7763c2bab92c619cdd9294c69004f89e136c0afc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/database": "^10.45|^11.0|^12.0",
|
||||
"php": "^8.1",
|
||||
"spatie/laravel-tags": "^4.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Filament\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Filament support for `spatie/laravel-tags`.",
|
||||
"homepage": "https://github.com/filamentphp/filament",
|
||||
"support": {
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2025-05-19T07:27:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/support",
|
||||
"version": "v4.0.7",
|
||||
@@ -5566,6 +5603,80 @@
|
||||
},
|
||||
"time": "2021-02-08T20:43:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/eloquent-sortable",
|
||||
"version": "4.5.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spatie/eloquent-sortable.git",
|
||||
"reference": "c1c4f3a66cd41eb7458783c8a4c8e5d7924a9f20"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/eloquent-sortable/zipball/c1c4f3a66cd41eb7458783c8a4c8e5d7924a9f20",
|
||||
"reference": "c1c4f3a66cd41eb7458783c8a4c8e5d7924a9f20",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/database": "^9.31|^10.0|^11.0|^12.0",
|
||||
"illuminate/support": "^9.31|^10.0|^11.0|^12.0",
|
||||
"nesbot/carbon": "^2.63|^3.0",
|
||||
"php": "^8.1",
|
||||
"spatie/laravel-package-tools": "^1.9"
|
||||
},
|
||||
"require-dev": {
|
||||
"orchestra/testbench": "^7.0|^8.0|^9.0|^10.0",
|
||||
"phpunit/phpunit": "^9.5|^10.0|^11.5.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Spatie\\EloquentSortable\\EloquentSortableServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Spatie\\EloquentSortable\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Freek Van der Herten",
|
||||
"email": "freek@spatie.be"
|
||||
}
|
||||
],
|
||||
"description": "Sortable behaviour for eloquent models",
|
||||
"homepage": "https://github.com/spatie/eloquent-sortable",
|
||||
"keywords": [
|
||||
"behaviour",
|
||||
"eloquent",
|
||||
"laravel",
|
||||
"model",
|
||||
"sort",
|
||||
"sortable"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/spatie/eloquent-sortable/issues",
|
||||
"source": "https://github.com/spatie/eloquent-sortable/tree/4.5.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://spatie.be/open-source/support-us",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/spatie",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-25T11:46:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/invade",
|
||||
"version": "2.1.0",
|
||||
@@ -5686,6 +5797,159 @@
|
||||
],
|
||||
"time": "2025-07-17T15:46:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/laravel-tags",
|
||||
"version": "4.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spatie/laravel-tags.git",
|
||||
"reference": "9fc59a9328e892bbb5b01c948b0d703e22d543ec"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/laravel-tags/zipball/9fc59a9328e892bbb5b01c948b0d703e22d543ec",
|
||||
"reference": "9fc59a9328e892bbb5b01c948b0d703e22d543ec",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"laravel/framework": "^10.0|^11.0|^12.0",
|
||||
"nesbot/carbon": "^2.63|^3.0",
|
||||
"php": "^8.1",
|
||||
"spatie/eloquent-sortable": "^4.0",
|
||||
"spatie/laravel-package-tools": "^1.4",
|
||||
"spatie/laravel-translatable": "^6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"orchestra/testbench": "^8.0|^9.0|^10.0",
|
||||
"pestphp/pest": "^1.22|^2.0",
|
||||
"phpunit/phpunit": "^9.5.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Spatie\\Tags\\TagsServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Spatie\\Tags\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Freek Van der Herten",
|
||||
"email": "freek@spatie.be",
|
||||
"homepage": "https://spatie.be",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Add tags and taggable behaviour to your Laravel app",
|
||||
"homepage": "https://github.com/spatie/laravel-tags",
|
||||
"keywords": [
|
||||
"laravel-tags",
|
||||
"spatie"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/spatie/laravel-tags/issues",
|
||||
"source": "https://github.com/spatie/laravel-tags/tree/4.10.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/spatie",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-08T07:49:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/laravel-translatable",
|
||||
"version": "6.11.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spatie/laravel-translatable.git",
|
||||
"reference": "032d85b28de315310dab2048b857016f1194f68b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/laravel-translatable/zipball/032d85b28de315310dab2048b857016f1194f68b",
|
||||
"reference": "032d85b28de315310dab2048b857016f1194f68b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/database": "^10.0|^11.0|^12.0",
|
||||
"illuminate/support": "^10.0|^11.0|^12.0",
|
||||
"php": "^8.0",
|
||||
"spatie/laravel-package-tools": "^1.11"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.64",
|
||||
"mockery/mockery": "^1.4",
|
||||
"orchestra/testbench": "^7.0|^8.0|^9.0|^10.0",
|
||||
"pestphp/pest": "^1.20|^2.0|^3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"aliases": {
|
||||
"Translatable": "Spatie\\Translatable\\Facades\\Translatable"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Spatie\\Translatable\\TranslatableServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Spatie\\Translatable\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Freek Van der Herten",
|
||||
"email": "freek@spatie.be",
|
||||
"homepage": "https://spatie.be",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Sebastian De Deyne",
|
||||
"email": "sebastian@spatie.be",
|
||||
"homepage": "https://spatie.be",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A trait to make an Eloquent model hold translations",
|
||||
"homepage": "https://github.com/spatie/laravel-translatable",
|
||||
"keywords": [
|
||||
"eloquent",
|
||||
"i8n",
|
||||
"laravel-translatable",
|
||||
"model",
|
||||
"multilingual",
|
||||
"spatie",
|
||||
"translate"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/spatie/laravel-translatable/issues",
|
||||
"source": "https://github.com/spatie/laravel-translatable/tree/6.11.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/spatie",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-20T15:51:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/shiki-php",
|
||||
"version": "2.3.2",
|
||||
@@ -5751,6 +6015,79 @@
|
||||
],
|
||||
"time": "2025-02-21T14:16:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "stephenjude/filament-blog",
|
||||
"version": "4.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/stephenjude/filament-blog.git",
|
||||
"reference": "040414004f876e880889e8d1646de219d85365bc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/stephenjude/filament-blog/zipball/040414004f876e880889e8d1646de219d85365bc",
|
||||
"reference": "040414004f876e880889e8d1646de219d85365bc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"filament/filament": "^4.0",
|
||||
"filament/spatie-laravel-tags-plugin": "^v3.0",
|
||||
"php": "^8.3",
|
||||
"spatie/laravel-package-tools": "^1.16.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.13.1",
|
||||
"nunomaduro/collision": "^7.8.1|^8.0",
|
||||
"orchestra/testbench": "^9.0|^10.0",
|
||||
"pestphp/pest": "^2.18.2|^3.7",
|
||||
"pestphp/pest-plugin-arch": "^2.3.3|^3.0",
|
||||
"pestphp/pest-plugin-laravel": "^2.2|^3.1",
|
||||
"pestphp/pest-plugin-livewire": "^2.1|^3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Stephenjude\\FilamentBlog\\BlogServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Stephenjude\\FilamentBlog\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "stephenjude",
|
||||
"email": "stephenjudesuccess@gmail.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Filament Blog Builder",
|
||||
"homepage": "https://github.com/stephenjude/filament-blog",
|
||||
"keywords": [
|
||||
"blog",
|
||||
"filament-blog",
|
||||
"laravel",
|
||||
"stephenjude"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/stephenjude/filament-blog/issues",
|
||||
"source": "https://github.com/stephenjude/filament-blog/tree/4.2.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/stephenjude",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-09-08T07:49:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "stripe/stripe-php",
|
||||
"version": "v17.6.0",
|
||||
|
||||
@@ -78,7 +78,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'locale' => env('APP_LOCALE', 'en'),
|
||||
'locale' => env('APP_LOCALE', 'de'),
|
||||
|
||||
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
|
||||
|
||||
|
||||
81
config/filament-blog.php
Normal file
81
config/filament-blog.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'post_model' => \App\Models\BlogPost::class,
|
||||
'category_model' => \App\Models\BlogCategory::class,
|
||||
'tag_model' => \App\Models\BlogTag::class,
|
||||
'panels' => [
|
||||
'superadmin' => [
|
||||
'resources' => [
|
||||
\Stephenjude\FilamentBlog\Filament\Resources\PostResource::class,
|
||||
\Stephenjude\FilamentBlog\Filament\Resources\CategoryResource::class,
|
||||
\Stephenjude\FilamentBlog\Filament\Resources\TagResource::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
/**
|
||||
* Supported content editors: richtext & markdown:
|
||||
* \Filament\Forms\Components\RichEditor::class
|
||||
* \Filament\Forms\Components\MarkdownEditor::class
|
||||
*/
|
||||
'editor' => \Filament\Forms\Components\RichEditor::class,
|
||||
|
||||
/**
|
||||
* Configs for Posts banner file that give you option to change
|
||||
* Disk,Directory name , maximum upload size and the
|
||||
* Crop aspect ratio of the Posts banner image.
|
||||
*/
|
||||
'banner' => [
|
||||
'disk' => 'public',
|
||||
'directory' => 'blog',
|
||||
'maxSize' => 5120,
|
||||
'cropAspectRatio' => '16:9',
|
||||
'visibility' => 'public',
|
||||
],
|
||||
|
||||
/**
|
||||
* Configs for Posts that give you option to change
|
||||
* the sort column and direction of the Posts.
|
||||
*/
|
||||
'sort' => [
|
||||
'column' => 'published_at',
|
||||
'direction' => 'desc',
|
||||
],
|
||||
|
||||
/**
|
||||
* Configs for Author Avatar file that give you option to change
|
||||
* Disk,Directory name , maximum upload size and the
|
||||
* Of the Author avatar image.
|
||||
*/
|
||||
'avatar' => [
|
||||
'disk' => 'public',
|
||||
'directory' => 'blog',
|
||||
'maxSize' => 5120,
|
||||
'visibility' => 'public',
|
||||
],
|
||||
|
||||
/**
|
||||
* Buttons for text editor toolbar.
|
||||
*/
|
||||
'toolbar_buttons' => [
|
||||
'attachFiles',
|
||||
'blockquote',
|
||||
'bold',
|
||||
'bulletList',
|
||||
'codeBlock',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'hr',
|
||||
'image',
|
||||
'italic',
|
||||
'link',
|
||||
'orderedList',
|
||||
'redo',
|
||||
'strike',
|
||||
'table',
|
||||
'underline',
|
||||
'undo',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class () extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('blog_categories', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('slug')->unique();
|
||||
$table->longText('description')->nullable();
|
||||
$table->boolean('is_visible')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('blog_authors', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->string('photo')->nullable();
|
||||
$table->longText('bio')->nullable();
|
||||
$table->string('github_handle')->nullable();
|
||||
$table->string('twitter_handle')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('blog_posts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('blog_author_id')->nullable()->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('blog_category_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('title');
|
||||
$table->string('slug')->unique();
|
||||
$table->text('excerpt')->nullable();
|
||||
$table->string('banner')->nullable();
|
||||
$table->longText('content');
|
||||
$table->date('published_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('blog_posts');
|
||||
Schema::dropIfExists('blog_categories');
|
||||
Schema::dropIfExists('blog_authors');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('blog_posts', function (Blueprint $table) {
|
||||
$table->json('translations')->nullable();
|
||||
});
|
||||
|
||||
Schema::table('blog_categories', function (Blueprint $table) {
|
||||
$table->json('translations')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('blog_posts', function (Blueprint $table) {
|
||||
$table->dropColumn('translations');
|
||||
});
|
||||
|
||||
Schema::table('blog_categories', function (Blueprint $table) {
|
||||
$table->dropColumn('translations');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->string('email')->nullable()->after('slug');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->dropColumn('email');
|
||||
});
|
||||
}
|
||||
};
|
||||
36
database/migrations/2025_09_26_153139_create_tag_tables.php
Normal file
36
database/migrations/2025_09_26_153139_create_tag_tables.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('tags', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->json('name');
|
||||
$table->json('slug');
|
||||
$table->string('type')->nullable();
|
||||
$table->integer('order_column')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('taggables', function (Blueprint $table) {
|
||||
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
|
||||
|
||||
$table->morphs('taggable');
|
||||
|
||||
$table->unique(['tag_id', 'taggable_id', 'taggable_type']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('taggables');
|
||||
Schema::dropIfExists('tags');
|
||||
}
|
||||
};
|
||||
11146
public/vendor/livewire/livewire.esm.js
vendored
Normal file
11146
public/vendor/livewire/livewire.esm.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7
public/vendor/livewire/livewire.esm.js.map
vendored
Normal file
7
public/vendor/livewire/livewire.esm.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
10234
public/vendor/livewire/livewire.js
vendored
Normal file
10234
public/vendor/livewire/livewire.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
103
public/vendor/livewire/livewire.min.js
vendored
Normal file
103
public/vendor/livewire/livewire.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
public/vendor/livewire/livewire.min.js.map
vendored
Normal file
7
public/vendor/livewire/livewire.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
public/vendor/livewire/manifest.json
vendored
Normal file
2
public/vendor/livewire/manifest.json
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
{"/livewire.js":"df3a17f2"}
|
||||
@@ -3,111 +3,158 @@
|
||||
<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.">
|
||||
<title>Fotospiel - Event-Fotos einfach und sicher mit QR-Codes</title>
|
||||
<meta name="description" content="Sammle Gastfotos für Events mit QR-Codes und 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'])
|
||||
<style>
|
||||
@keyframes aurora {
|
||||
0%, 100% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
}
|
||||
.bg-aurora {
|
||||
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
|
||||
background-size: 400% 400%;
|
||||
animation: aurora 15s ease infinite;
|
||||
}
|
||||
</style>
|
||||
</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">
|
||||
<!-- Header -->
|
||||
<header class="bg-white shadow-md sticky top-0 z-50">
|
||||
<div class="container mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<a href="/" class="text-2xl font-bold text-gray-900">Fotospiel</a>
|
||||
<svg class="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<nav class="hidden md:flex space-x-6 items-center">
|
||||
<a href="#how-it-works" class="text-gray-600 hover:text-gray-900">How it works</a>
|
||||
<a href="#features" class="text-gray-600 hover:text-gray-900">Features</a>
|
||||
<div class="relative group">
|
||||
<button class="text-gray-600 hover:text-gray-900">Occasions</button>
|
||||
<div class="absolute top-full left-0 mt-2 bg-white border rounded shadow-lg hidden group-hover:block">
|
||||
<a href="/occasions/weddings" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Weddings</a>
|
||||
<a href="/occasions/birthdays" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Birthdays</a>
|
||||
<a href="/occasions/corporate-events" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Corporate Events</a>
|
||||
<a href="/occasions/family-celebrations" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Family Celebrations</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/blog" class="text-gray-600 hover:text-gray-900">Blog</a>
|
||||
<a href="#pricing" class="text-gray-600 hover:text-gray-900">Pricing</a>
|
||||
<a href="#contact" class="text-gray-600 hover:text-gray-900">Contact</a>
|
||||
<a href="/buy-credits/basic" class="bg-[#FFB6C1] text-white px-6 py-2 rounded-full font-semibold hover:bg-[#FF69B4] transition">Jetzt starten</a>
|
||||
</nav>
|
||||
<!-- Mobile Menu Placeholder (Hamburger) -->
|
||||
<button class="md:hidden text-gray-600">☰</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Hero Section id="hero" -->
|
||||
<section id="hero" class="bg-aurora text-white py-20 px-4">
|
||||
<div class="container mx-auto flex flex-col md:flex-row items-center gap-8 max-w-6xl">
|
||||
<div class="md:w-1/2 text-center md:text-left">
|
||||
<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>
|
||||
<p class="text-xl md:text-2xl mb-8">Sammle Gastfotos für Events mit QR-Codes. Unsere sichere PWA-Plattform für Gäste und Organisatoren – einfach, mobil und datenschutzkonform. Besser als Konkurrenz, geliebt von Tausenden.</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>
|
||||
<div class="md:w-1/2">
|
||||
<img src="https://images.unsplash.com/photo-1511285560929-80b456fea0bc?w=600&h=400&fit=crop" alt="Event-Fotos mit QR" class="rounded-lg shadow-lg w-full" style="filter: drop-shadow(0 10px 8px rgba(0,0,0,0.1));">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features Section -->
|
||||
<section class="py-20 px-4 bg-white">
|
||||
<!-- How it works Section id="how-it-works" -->
|
||||
<section id="how-it-works" class="py-20 px-4 bg-gray-50">
|
||||
<div class="container mx-auto">
|
||||
<h2 class="text-3xl font-bold text-center mb-12">Warum Fotospiel?</h2>
|
||||
<h2 class="text-3xl font-bold text-center mb-12">So funktioniert es – in 4 einfachen Schritten mit QR-Codes</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 & QR generieren</h3>
|
||||
<p>Als Organisator: Registrieren, Event anlegen, QR-Code erstellen und drucken/teilen.</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 via QR</h3>
|
||||
<p>Gäste: QR scannen, 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 – alles anonym.</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 und QR archivieren.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features Section id="features" -->
|
||||
<section id="features" class="py-20 px-4 bg-white">
|
||||
<div class="container mx-auto">
|
||||
<h2 class="text-3xl font-bold text-center mb-12">Warum Fotospiel mit QR?</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>
|
||||
<h3 class="text-xl font-semibold mb-2">Sichere QR-Uploads</h3>
|
||||
<p>GDPR-konform, anonyme Sessions, QR-basierte Zugriffe ohne 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>
|
||||
<h3 class="text-xl font-semibold mb-2">Mobile PWA & QR</h3>
|
||||
<p>Offline-fähig, App-ähnlich für iOS/Android, QR-Scan für schnellen Einstieg.</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>
|
||||
<h3 class="text-xl font-semibold mb-2">Schnell & Einfach mit QR</h3>
|
||||
<p>Automatische Thumbnails, Echtzeit-Updates, QR-Sharing für Gäste.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4-Schritte Section -->
|
||||
<section class="py-20 px-4 bg-gray-50">
|
||||
<!-- Pricing Section id="pricing" -->
|
||||
<section id="pricing" 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>
|
||||
<h2 class="text-3xl font-bold text-center mb-12">Tarife für QR-Events</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">
|
||||
<div class="bg-white 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>1 Event mit QR</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]">
|
||||
<div class="bg-white 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>10 Events mit QR</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">
|
||||
<div class="bg-white 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>50 Events mit QR</li>
|
||||
<li>Support & Custom</li>
|
||||
<li>Alle Features</li>
|
||||
</ul>
|
||||
@@ -117,8 +164,8 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Kontakt Section -->
|
||||
<section class="py-20 px-4 bg-gray-50">
|
||||
<!-- Contact Section id="contact" -->
|
||||
<section id="contact" class="py-20 px-4 bg-white">
|
||||
<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">
|
||||
@@ -153,16 +200,16 @@
|
||||
</section>
|
||||
|
||||
<!-- Testimonials Section -->
|
||||
<section class="py-20 px-4 bg-white">
|
||||
<section class="py-20 px-4 bg-gray-50">
|
||||
<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>
|
||||
<div class="bg-white p-6 rounded-lg">
|
||||
<p class="mb-4">"Perfekt für unsere Hochzeit! QR-Sharing war super einfach."</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>
|
||||
<div class="bg-white p-6 rounded-lg">
|
||||
<p class="mb-4">"Großes Firmenevent – alle Fotos zentral via QR."</p>
|
||||
<p class="font-semibold">- Team XYZ GmbH</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -170,21 +217,21 @@
|
||||
</section>
|
||||
|
||||
<!-- FAQ Section -->
|
||||
<section class="py-20 px-4 bg-gray-50">
|
||||
<section class="py-20 px-4 bg-white">
|
||||
<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">
|
||||
<div class="bg-gray-50 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>
|
||||
<p>Ja, der Basic-Tarif ist kostenlos für 1 Event mit QR. Upgrades ab 99€.</p>
|
||||
</div>
|
||||
<div class="bg-white p-4 rounded-lg">
|
||||
<div class="bg-gray-50 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>
|
||||
<p>100% GDPR-konform. Keine personenbezogenen Daten gespeichert. QR-Zugriffe anonym. 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 class="bg-gray-50 p-4 rounded-lg">
|
||||
<h3 class="font-semibold">Wie funktioniert QR-Sharing?</h3>
|
||||
<p>Generiere QR im Dashboard, teile es – Gäste scannen, laden Fotos hoch in der PWA.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -197,7 +244,7 @@
|
||||
<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>
|
||||
<a href="#contact" class="hover:text-[#FFB6C1]">Kontakt</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
89
resources/views/marketing/blog-show.blade.php
Normal file
89
resources/views/marketing/blog-show.blade.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<!doctype html>
|
||||
<html lang="{{ app()->getLocale() }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ $post->meta_title ?? $post->title }} - Fotospiel</title>
|
||||
<meta name="description" content="{{ $post->meta_description ?? $post->excerpt }}">
|
||||
<meta property="og:title" content="{{ $post->meta_title ?? $post->title }}">
|
||||
<meta property="og:description" content="{{ $post->meta_description ?? $post->excerpt }}">
|
||||
<meta property="og:image" content="{{ $post->featured_image }}">
|
||||
<meta property="og:url" content="{{ route('blog.show', $post) }}">
|
||||
<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">
|
||||
<!-- Shared Header -->
|
||||
<header class="bg-white shadow-md sticky top-0 z-50">
|
||||
<div class="container mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<a href="/marketing" class="text-2xl font-bold text-gray-900">Fotospiel</a>
|
||||
<svg class="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
|
||||
</div>
|
||||
<nav class="hidden md:flex space-x-6">
|
||||
<a href="/marketing#how-it-works" class="text-gray-600 hover:text-gray-900">How it works</a>
|
||||
<a href="/marketing#features" class="text-gray-600 hover:text-gray-900">Features</a>
|
||||
<div class="relative">
|
||||
<button class="text-gray-600 hover:text-gray-900">Occasions</button>
|
||||
<div class="absolute top-full left-0 mt-2 bg-white border rounded shadow-lg">
|
||||
<a href="/occasions/weddings" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Weddings</a>
|
||||
<a href="/occasions/birthdays" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Birthdays</a>
|
||||
<a href="/occasions/corporate-events" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Corporate Events</a>
|
||||
<a href="/occasions/family-celebrations" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Family Celebrations</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/blog" class="text-gray-900 font-semibold">Blog</a>
|
||||
<a href="/marketing#pricing" class="text-gray-600 hover:text-gray-900">Pricing</a>
|
||||
<a href="/marketing#contact" class="text-gray-600 hover:text-gray-900">Contact</a>
|
||||
</nav>
|
||||
<a href="/buy-credits/basic" class="bg-[#FFB6C1] text-white px-6 py-2 rounded-full font-semibold">Jetzt starten</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Blog Post Hero -->
|
||||
<section class="py-20 px-4 bg-gradient-to-r from-[#FFB6C1] via-[#FFD700] to-[#87CEEB] text-white">
|
||||
<div class="container mx-auto max-w-4xl">
|
||||
@if ($post->featured_image)
|
||||
<img src="{{ $post->featured_image }}" alt="{{ $post->title }}" class="w-full h-64 object-cover rounded mb-8">
|
||||
@endif
|
||||
<h1 class="text-4xl md:text-5xl font-bold mb-4">{{ $post->title }}</h1>
|
||||
<p class="text-xl mb-8">{{ $post->excerpt }}</p>
|
||||
<p class="text-sm text-gray-200">Veröffentlicht am {{ $post->published_at->format('d.m.Y') }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Blog Post Content -->
|
||||
<section class="py-20 px-4">
|
||||
<div class="container mx-auto max-w-4xl">
|
||||
<div class="prose max-w-none">
|
||||
{!! $post->content !!}
|
||||
</div>
|
||||
<div class="mt-8 p-4 bg-gray-100 rounded">
|
||||
<h3 class="font-semibold mb-2">Kategorien:</h3>
|
||||
@foreach ($post->categories as $category)
|
||||
<span class="inline-block bg-[#FFB6C1] text-white px-2 py-1 rounded text-sm mr-2 mb-2">{{ $category->name }}</span>
|
||||
@endforeach
|
||||
<h3 class="font-semibold mt-4 mb-2">Tags:</h3>
|
||||
@foreach ($post->tags as $tag)
|
||||
<span class="inline-block bg-gray-200 text-gray-800 px-2 py-1 rounded text-sm mr-2 mb-2">#{{ $tag->name }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="mt-8 text-center">
|
||||
<a href="/blog" class="bg-[#FFB6C1] text-white px-6 py-2 rounded-full font-semibold">Zurück zum Blog</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-gray-800 text-white py-8 px-4 mt-20">
|
||||
<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="/marketing#contact" class="hover:text-[#FFB6C1]">Kontakt</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
89
resources/views/marketing/blog.blade.php
Normal file
89
resources/views/marketing/blog.blade.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<!doctype html>
|
||||
<html lang="{{ app()->getLocale() }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Fotospiel - Blog</title>
|
||||
<meta name="description" content="Tipps, News und Anleitungen zu Event-Fotos mit QR-Codes und PWA.">
|
||||
<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">
|
||||
<!-- Shared Header (wie in occasions.blade.php) -->
|
||||
<header class="bg-white shadow-md sticky top-0 z-50">
|
||||
<div class="container mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<a href="/marketing" class="text-2xl font-bold text-gray-900">Fotospiel</a>
|
||||
<svg class="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
|
||||
</div>
|
||||
<nav class="hidden md:flex space-x-6">
|
||||
<a href="/marketing#how-it-works" class="text-gray-600 hover:text-gray-900">How it works</a>
|
||||
<a href="/marketing#features" class="text-gray-600 hover:text-gray-900">Features</a>
|
||||
<div class="relative">
|
||||
<button class="text-gray-600 hover:text-gray-900">Occasions</button>
|
||||
<div class="absolute top-full left-0 mt-2 bg-white border rounded shadow-lg">
|
||||
<a href="/occasions/weddings" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Weddings</a>
|
||||
<a href="/occasions/birthdays" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Birthdays</a>
|
||||
<a href="/occasions/corporate-events" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Corporate Events</a>
|
||||
<a href="/occasions/family-celebrations" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Family Celebrations</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/blog" class="text-gray-900 font-semibold">Blog</a>
|
||||
<a href="/marketing#pricing" class="text-gray-600 hover:text-gray-900">Pricing</a>
|
||||
<a href="/marketing#contact" class="text-gray-600 hover:text-gray-900">Contact</a>
|
||||
</nav>
|
||||
<a href="/buy-credits/basic" class="bg-[#FFB6C1] text-white px-6 py-2 rounded-full font-semibold">Jetzt starten</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Hero for Blog -->
|
||||
<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 Blog</h1>
|
||||
<p class="text-xl md:text-2xl mb-8 max-w-3xl mx-auto">Tipps, News und Anleitungen zu perfekten Event-Fotos mit QR-Codes, PWA und mehr. Bleib informiert!</p>
|
||||
<a href="/marketing#how-it-works" class="bg-white text-[#FFB6C1] px-8 py-4 rounded-full font-semibold text-lg hover:bg-gray-100 transition">Mehr über Fotospiel</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Blog Posts Section -->
|
||||
<section class="py-20 px-4 bg-white">
|
||||
<div class="container mx-auto max-w-4xl">
|
||||
<h2 class="text-3xl font-bold text-center mb-12">Aktuelle Blog-Posts</h2>
|
||||
@if ($posts->count() > 0)
|
||||
<div class="grid md:grid-cols-2 gap-8">
|
||||
@foreach ($posts as $post)
|
||||
<div class="bg-gray-50 p-6 rounded-lg">
|
||||
@if ($post->featured_image)
|
||||
<img src="{{ $post->featured_image }}" alt="{{ $post->title }}" class="w-full h-48 object-cover rounded mb-4">
|
||||
@endif
|
||||
<h3 class="text-xl font-semibold mb-2"><a href="{{ route('blog.show', $post->slug) }}" class="hover:text-[#FFB6C1]">{{ $post->title }}</a></h3>
|
||||
<p class="mb-4">{{ Str::limit($post->excerpt, 150) }}</p>
|
||||
<p class="text-sm text-gray-500 mb-4">Veröffentlicht am {{ $post->published_at->format('d.m.Y') }}</p>
|
||||
<a href="{{ route('blog.show', $post->slug) }}" class="text-[#FFB6C1] font-semibold">Lesen</a>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@if ($posts->hasPages())
|
||||
<div class="mt-12 text-center">
|
||||
{{ $posts->links() }}
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<p class="text-center text-gray-600">Noch keine Posts verfügbar. Bleib dran!</p>
|
||||
@endif
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer (wie in occasions.blade.php) -->
|
||||
<footer class="bg-gray-800 text-white py-8 px-4 mt-20">
|
||||
<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="/marketing#contact" class="hover:text-[#FFB6C1]">Kontakt</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
137
resources/views/marketing/occasions.blade.php
Normal file
137
resources/views/marketing/occasions.blade.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Fotospiel - {{ ucfirst($type) }} Occasion</title>
|
||||
<meta name="description" content="Fotospiel für {{ ucfirst($type) }}: Sammle Gastfotos mit QR-Codes.">
|
||||
<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">
|
||||
<!-- Shared Header (kopiert aus marketing, vereinfacht) -->
|
||||
<header class="bg-white shadow-md sticky top-0 z-50">
|
||||
<div class="container mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<a href="/marketing" class="text-2xl font-bold text-gray-900">Fotospiel</a>
|
||||
<svg class="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
|
||||
</div>
|
||||
<nav class="hidden md:flex space-x-6">
|
||||
<a href="/marketing#how-it-works" class="text-gray-600 hover:text-gray-900">How it works</a>
|
||||
<a href="/marketing#features" class="text-gray-600 hover:text-gray-900">Features</a>
|
||||
<div class="relative">
|
||||
<button class="text-gray-600 hover:text-gray-900">Occasions</button>
|
||||
<div class="absolute top-full left-0 mt-2 bg-white border rounded shadow-lg">
|
||||
<a href="/occasions/weddings" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Weddings</a>
|
||||
<a href="/occasions/birthdays" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Birthdays</a>
|
||||
<a href="/occasions/corporate-events" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Corporate Events</a>
|
||||
<a href="/occasions/family-celebrations" class="block px-4 py-2 text-gray-600 hover:text-gray-900">Family Celebrations</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/blog" class="text-gray-600 hover:text-gray-900">Blog</a>
|
||||
<a href="/marketing#pricing" class="text-gray-600 hover:text-gray-900">Pricing</a>
|
||||
<a href="/marketing#contact" class="text-gray-600 hover:text-gray-900">Contact</a>
|
||||
</nav>
|
||||
<a href="/buy-credits/basic" class="bg-[#FFB6C1] text-white px-6 py-2 rounded-full font-semibold">Jetzt starten</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Hero for Occasion -->
|
||||
<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 für {{ ucfirst($type) }}</h1>
|
||||
<p class="text-xl md:text-2xl mb-8 max-w-3xl mx-auto">Sammle unvergessliche Fotos von deinen Gästen mit QR-Codes. Perfekt für {{ ucfirst($type) }} – 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">Event starten</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Occasion Specific Content -->
|
||||
<section class="py-20 px-4 bg-white">
|
||||
<div class="container mx-auto max-w-4xl">
|
||||
@if($type === 'weddings')
|
||||
<h2 class="text-3xl font-bold text-center mb-12">Hochzeiten mit Fotospiel</h2>
|
||||
<p class="text-lg mb-8 text-center">Erfange romantische Momente: Gäste teilen Fotos via QR, wähle Emotions wie 'Romantisch' oder 'Fröhlich'. Besser als traditionelle Fotoboxen.</p>
|
||||
<div class="grid md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<img src="https://images.unsplash.com/photo-1515934751635-c81c6bc9a2d8?w=600&h=400&fit=crop" alt="Hochzeitsfotos" class="rounded-lg shadow-lg">
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-2xl font-semibold">Vorteile für Hochzeiten</h3>
|
||||
<ul class="space-y-2">
|
||||
<li>• QR-Code für Gäste: Einfaches Teilen ohne App-Download.</li>
|
||||
<li>• Emotion-Filter: Kategorisiere Fotos (z.B. 'Tanz', 'Kuss').</li>
|
||||
<li>• Private Galerie: Nur freigegebene Fotos sichtbar.</li>
|
||||
<li>• Download: Hochauflösend für Album.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@elseif($type === 'birthdays')
|
||||
<h2 class="text-3xl font-bold text-center mb-12">Geburtstage feiern</h2>
|
||||
<p class="text-lg mb-8 text-center">Lass Freunde und Familie spontane Fotos teilen. QR auf der Torte – Spaß garantiert!</p>
|
||||
<div class="grid md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<img src="https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?w=600&h=400&fit=crop" alt="Geburtstagsfotos" class="rounded-lg shadow-lg">
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-2xl font-semibold">Vorteile für Geburtstage</h3>
|
||||
<ul class="space-y-2">
|
||||
<li>• Schnelle Uploads: Kamera oder Galerie.</li>
|
||||
<li>• Likes & Shares: Beliebte Momente hervorheben.</li>
|
||||
<li>• Offline-fähig: PWA funktioniert ohne Internet.</li>
|
||||
<li>• Anonym: Keine Registrierung nötig.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@elseif($type === 'corporate-events')
|
||||
<h2 class="text-3xl font-bold text-center mb-12">Firmenevents professionell</h2>
|
||||
<p class="text-lg mb-8 text-center">Netzwerken und Team-Building: Sammle Fotos zentral, teile Highlights intern.</p>
|
||||
<div class="grid md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<img src="https://images.unsplash.com/photo-1521737604893-d14cc237f11d?w=600&h=400&fit=crop" alt="Firmenevent-Fotos" class="rounded-lg shadow-lg">
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-2xl font-semibold">Vorteile für Firmenevents</h3>
|
||||
<ul class="space-y-2">
|
||||
<li>• QR an Ständen: Gäste fotografieren sich selbst.</li>
|
||||
<li>• Kategorien: 'Team', 'Netzwerk', 'Präsentation'.</li>
|
||||
<li>• Export: Für Social Media oder Intranet.</li>
|
||||
<li>• GDPR-sicher: Keine PII gespeichert.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@elseif($type === 'family-celebrations')
|
||||
<h2 class="text-3xl font-bold text-center mb-12">Familienfeiern</h2>
|
||||
<p class="text-lg mb-8 text-center">Von Taufen bis Jubiläen: Sammle Erinnerungen von allen Verwandten.</p>
|
||||
<div class="grid md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=600&h=400&fit=crop" alt="Familienfotos" class="rounded-lg shadow-lg">
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-2xl font-semibold">Vorteile für Familienfeiern</h3>
|
||||
<ul class="space-y-2">
|
||||
<li>• Einfach für alle Altersgruppen: Große Buchstaben, Touch-freundlich.</li>
|
||||
<li>• Emotionen: 'Familie', 'Glück', 'Zusammenhalt'.</li>
|
||||
<li>• Teilen: Per Link oder QR für Nachfeier.</li>
|
||||
<li>• Unbegrenzt: Im Premium-Tarif.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<p class="text-center">Occasion nicht gefunden. <a href="/marketing">Zurück zur Startseite</a>.</p>
|
||||
@endif
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer (kopiert aus marketing) -->
|
||||
<footer class="bg-gray-800 text-white py-8 px-4 mt-20">
|
||||
<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="/marketing#contact" class="hover:text-[#FFB6C1]">Kontakt</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,8 +3,15 @@
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Inertia\Inertia;
|
||||
|
||||
// Marketing-Seite unter Root
|
||||
// Marketing-Seite mit Locale-Prefix
|
||||
Route::prefix('{locale?}')->where(['locale' => 'de|en'])->middleware('locale')->group(function () {
|
||||
Route::view('/', 'marketing')->name('marketing');
|
||||
Route::get('/occasions/{type}', function ($type) {
|
||||
return view('marketing.occasions', ['type' => $type]);
|
||||
})->name('occasions.type');
|
||||
Route::get('/blog', [\App\Http\Controllers\MarketingController::class, 'blogIndex'])->name('blog');
|
||||
Route::get('/blog/{post}', [\App\Http\Controllers\MarketingController::class, 'blogShow'])->name('blog.show');
|
||||
});
|
||||
|
||||
Route::middleware(['auth', 'verified'])->group(function () {
|
||||
Route::get('dashboard', function () {
|
||||
|
||||
Reference in New Issue
Block a user