Marketing packages now use localized name/description data plus seeded placeholder-
driven breakdown tables, with frontend/cards/dialog updated accordingly (database/ migrations/2025_10_17_000001_add_description_table_to_packages.php, database/ migrations/2025_10_17_000002_add_translation_columns_to_packages.php, database/seeders/PackageSeeder.php, app/ Http/Controllers/MarketingController.php, resources/js/pages/marketing/Packages.tsx). Filament Package resource gains locale tabs, markdown editor, numeric/toggle inputs, and simplified feature management (app/Filament/Resources/PackageResource.php, app/Filament/Resources/PackageResource/Pages/ CreatePackage.php, .../EditPackage.php). Legal pages now render markdown-backed content inside the main layout via a new controller/view route setup and updated footer links (app/Http/Controllers/LegalPageController.php, routes/web.php, resources/views/partials/ footer.blade.php, resources/js/pages/legal/Show.tsx, remove old static pages). Translation files and shared assets updated to cover new marketing/legal strings and styling tweaks (public/ lang/*/marketing.json, resources/lang/*/marketing.php, resources/css/app.css, resources/js/admin/components/ LanguageSwitcher.tsx).
This commit is contained in:
@@ -480,14 +480,22 @@ class MarketingController extends Controller
|
||||
|
||||
public function packagesIndex()
|
||||
{
|
||||
$endcustomerPackages = Package::where('type', 'endcustomer')->orderBy('price')->get()->map(function ($p) {
|
||||
return $p->append(['features', 'limits']);
|
||||
});
|
||||
$resellerPackages = Package::where('type', 'reseller')->orderBy('price')->get()->map(function ($p) {
|
||||
return $p->append(['features', 'limits']);
|
||||
});
|
||||
$endcustomerPackages = Package::where('type', 'endcustomer')
|
||||
->orderBy('price')
|
||||
->get()
|
||||
->map(fn (Package $package) => $this->presentPackage($package))
|
||||
->values();
|
||||
|
||||
return Inertia::render('marketing/Packages', compact('endcustomerPackages', 'resellerPackages'));
|
||||
$resellerPackages = Package::where('type', 'reseller')
|
||||
->orderBy('price')
|
||||
->get()
|
||||
->map(fn (Package $package) => $this->presentPackage($package))
|
||||
->values();
|
||||
|
||||
return Inertia::render('marketing/Packages', [
|
||||
'endcustomerPackages' => $endcustomerPackages,
|
||||
'resellerPackages' => $resellerPackages,
|
||||
]);
|
||||
}
|
||||
|
||||
public function occasionsType($type)
|
||||
@@ -508,5 +516,170 @@ class MarketingController extends Controller
|
||||
|
||||
return Inertia::render('marketing/Occasions', ['type' => $type]);
|
||||
}
|
||||
}
|
||||
|
||||
private 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,
|
||||
];
|
||||
}
|
||||
|
||||
private 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),
|
||||
];
|
||||
}
|
||||
|
||||
private function applyPlaceholders(string $template, array $replacements): string
|
||||
{
|
||||
if ($template === '') {
|
||||
return $template;
|
||||
}
|
||||
|
||||
return str_replace(array_keys($replacements), array_values($replacements), $template);
|
||||
}
|
||||
|
||||
private 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);
|
||||
}
|
||||
|
||||
private 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';
|
||||
}
|
||||
|
||||
private 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 !== '')));
|
||||
}
|
||||
|
||||
private 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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user