Files
fotospiel-app/app/Support/Concerns/PresentsPackages.php
Codex Agent a949c8d3af - Wired the checkout wizard for Google “comfort login”: added Socialite controller + dependency, new Google env
hooks in config/services.php/.env.example, and updated wizard steps/controllers to store session payloads,
attach packages, and surface localized success/error states.
- Retooled payment handling for both Stripe and PayPal, adding richer status management in CheckoutController/
PayPalController, fallback flows in the wizard’s PaymentStep.tsx, and fresh feature tests for intent
creation, webhooks, and the wizard CTA.
- Introduced a consent-aware Matomo analytics stack: new consent context, cookie-banner UI, useAnalytics/
useCtaExperiment hooks, and MatomoTracker component, then instrumented marketing pages (Home, Packages,
Checkout) with localized copy and experiment tracking.
- Polished package presentation across marketing UIs by centralizing formatting in PresentsPackages, surfacing
localized description tables/placeholders, tuning badges/layouts, and syncing guest/marketing translations.
- Expanded docs & reference material (docs/prp/*, TODOs, public gallery overview) and added a Playwright smoke
test for the hero CTA while reconciling outstanding checklist items.
2025-10-19 11:41:03 +02:00

174 lines
5.7 KiB
PHP

<?php
namespace App\Support\Concerns;
use App\Models\Package;
trait PresentsPackages
{
protected function presentPackage(Package $package): array
{
$package->append('limits');
$packageArray = $package->toArray();
$features = $packageArray['features'] ?? [];
$features = $this->normaliseFeatures($features);
$locale = app()->getLocale();
$name = $this->resolveTranslation($package->name_translations ?? null, $package->name ?? '', $locale);
$descriptionTemplate = $this->resolveTranslation($package->description_translations ?? null, $package->description ?? '', $locale);
$replacements = $this->buildPlaceholderReplacements($package);
$description = trim($this->applyPlaceholders($descriptionTemplate, $replacements));
$table = $package->description_table ?? [];
if (is_string($table)) {
$decoded = json_decode($table, true);
$table = is_array($decoded) ? $decoded : [];
}
$table = array_map(function (array $row) use ($replacements) {
return [
'title' => trim($this->applyPlaceholders($row['title'] ?? '', $replacements)),
'value' => trim($this->applyPlaceholders($row['value'] ?? '', $replacements)),
];
}, $table);
$table = array_values($table);
$galleryDuration = $replacements['{{gallery_duration}}'] ?? null;
return [
'id' => $package->id,
'name' => $name,
'slug' => $package->slug,
'type' => $package->type,
'price' => $package->price,
'description' => $description,
'description_breakdown' => $table,
'gallery_duration_label' => $galleryDuration,
'events' => $package->type === 'endcustomer' ? 1 : ($package->max_events_per_year ?? null),
'features' => $features,
'limits' => $package->limits,
'max_photos' => $package->max_photos,
'max_guests' => $package->max_guests,
'max_tasks' => $package->max_tasks,
'gallery_days' => $package->gallery_days,
'max_events_per_year' => $package->max_events_per_year,
'watermark_allowed' => (bool) $package->watermark_allowed,
'branding_allowed' => (bool) $package->branding_allowed,
];
}
protected function buildPlaceholderReplacements(Package $package): array
{
$locale = app()->getLocale();
return [
'{{max_photos}}' => $this->formatCount($package->max_photos, [
'de' => 'unbegrenzt viele',
'en' => 'unlimited',
]),
'{{max_guests}}' => $this->formatCount($package->max_guests, [
'de' => 'beliebig viele',
'en' => 'any number of',
]),
'{{max_tasks}}' => $this->formatCount($package->max_tasks, [
'de' => 'individuelle',
'en' => 'custom',
]),
'{{max_events_per_year}}' => $this->formatCount($package->max_events_per_year, [
'de' => 'unbegrenzte',
'en' => 'unlimited',
]),
'{{gallery_duration}}' => $this->formatGalleryDuration($package->gallery_days),
];
}
protected function applyPlaceholders(string $template, array $replacements): string
{
if ($template === '') {
return $template;
}
return str_replace(array_keys($replacements), array_values($replacements), $template);
}
protected function formatCount(?int $value, array $fallbackByLocale): string
{
$locale = app()->getLocale();
if ($value === null) {
return $fallbackByLocale[$locale] ?? reset($fallbackByLocale) ?? '';
}
$decimal = $locale === 'de' ? ',' : '.';
$thousands = $locale === 'de' ? '.' : ',';
return number_format($value, 0, $decimal, $thousands);
}
protected function formatGalleryDuration(?int $days): string
{
$locale = app()->getLocale();
if (!$days || $days <= 0) {
return $locale === 'en' ? 'permanent' : 'dauerhaft';
}
if ($days % 30 === 0) {
$months = (int) ($days / 30);
if ($locale === 'en') {
return $months === 1 ? '1 month' : $months . ' months';
}
return $months === 1 ? '1 Monat' : $months . ' Monate';
}
return $locale === 'en' ? $days . ' days' : $days . ' Tage';
}
protected function normaliseFeatures(mixed $features): array
{
if (is_string($features)) {
$decoded = json_decode($features, true);
if (json_last_error() === JSON_ERROR_NONE) {
$features = $decoded;
}
}
if (! is_array($features)) {
return [];
}
$list = [];
foreach ($features as $key => $value) {
if (is_string($value)) {
$list[] = $value;
continue;
}
if (is_string($key) && (bool) $value) {
$list[] = $key;
}
}
return array_values(array_unique(array_filter($list, fn ($item) => is_string($item) && $item !== '')));
}
protected function resolveTranslation(mixed $value, string $fallback, string $locale): string
{
if (is_string($value)) {
$decoded = json_decode($value, true);
if (json_last_error() === JSON_ERROR_NONE) {
$value = $decoded;
}
}
if (is_array($value)) {
return trim((string) ($value[$locale] ?? $value['en'] ?? $value['de'] ?? $fallback));
}
return trim((string) ($value ?? $fallback));
}
}