Add Landing Page Section and Update Product Schema: Introduced a new LandingPageSection class to manage the layout and fields for the product landing page. Updated the ProductForm to include this new section, enhancing the product management interface. Added migration for landing_page_data in the products table and implemented localization for new fields in English and Turkish language files. Created a new landing Blade view to render the landing page dynamically, improving user experience and modularity.

This commit is contained in:
Ümit Tunç
2026-01-02 20:58:18 +03:00
parent 89eb9f5c4e
commit 97092dd793
10 changed files with 720 additions and 13 deletions
@@ -0,0 +1,228 @@
<?php
namespace App\Filament\Admin\Resources\Products\Schemas;
use App\Models\Language;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\FileUpload;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Components\Utilities\Get;
class LandingPageSection
{
private static function getIconOptions(): array
{
$icons = [
'cloud-network-2', 'touchscreen', 'lock', 'rocket', 'bar-chart', 'safe',
'controls', 'checked', 'calendar', 'currency', 'server', 'devices',
'search', 'compare', 'smartphone', 'headphone', 'secure', 'verified',
'wallet', 'shopping-cart', 'e-commerce', 'code', 'bulb', 'gears',
'shield', 'star', 'check-circle', 'bolt', 'zap', 'heart', 'gift',
];
return array_combine($icons, array_map(fn($icon) => ucwords(str_replace('-', ' ', $icon)), $icons));
}
private static function getBackgroundOptions(): array
{
$backgrounds = [
// Abstract backgrounds
'assets/img/photos/bg2.jpg' => 'Background 2 (Abstract)',
'assets/img/photos/bg3.jpg' => 'Background 3 (Abstract)',
'assets/img/photos/bg4.jpg' => 'Background 4 (Abstract)',
'assets/img/photos/bg13.jpg' => 'Background 13 (Abstract)',
// Gradient backgrounds
'assets/img/photos/bg14.png' => 'Background 14 (Gradient)',
'assets/img/photos/bg15.png' => 'Background 15 (Gradient)',
'assets/img/photos/bg16.png' => 'Background 16 (Gradient)',
'assets/img/photos/bg17.png' => 'Background 17 (Gradient)',
'assets/img/photos/bg18.png' => 'Background 18 (Gradient)',
'assets/img/photos/bg19.png' => 'Background 19 (Gradient)',
'assets/img/photos/bg20.png' => 'Background 20 (Gradient)',
'assets/img/photos/bg21.png' => 'Background 21 (Gradient)',
'assets/img/photos/bg22.png' => 'Background 22 (Gradient)',
'assets/img/photos/bg23.png' => 'Background 23 (Gradient)',
'assets/img/photos/bg24.png' => 'Background 24 (Gradient)',
'assets/img/photos/bg25.png' => 'Background 25 (Gradient)',
];
return $backgrounds;
}
public static function make(): Section
{
$languages = Language::active()->ordered()->get();
$iconOptions = self::getIconOptions();
$backgroundOptions = self::getBackgroundOptions();
return Section::make(__('products.landing_page_section'))
->schema([
// Hero Section
Section::make(__('products.hero_section'))
->schema([
TextInput::make('landing_page_data.hero.slogan')
->label(__('products.hero_slogan'))
->columnSpanFull(),
Textarea::make('landing_page_data.hero.sub_slogan')
->label(__('products.hero_sub_slogan'))
->rows(2)
->columnSpanFull(),
Select::make('landing_page_data.hero.background_image')
->label(__('products.hero_background_image'))
->options($backgroundOptions)
->searchable()
->placeholder(__('products.select_background'))
->helperText(__('products.hero_background_image_helper'))
->columnSpanFull(),
TextInput::make('landing_page_data.hero.app_store_link')
->label(__('products.app_store_link'))
->url()
->placeholder('https://apps.apple.com/...'),
TextInput::make('landing_page_data.hero.google_play_link')
->label(__('products.google_play_link'))
->url()
->placeholder('https://play.google.com/...'),
FileUpload::make('landing_page_data.hero.featured_image')
->label(__('products.hero_featured_image'))
->image()
->disk('public')
->directory('products/landing')
->columnSpanFull(),
])
->columns(2)
->collapsible()
->collapsed(),
// App Features Section
Section::make(__('products.app_features_section'))
->schema([
Repeater::make('landing_page_data.features')
->label(__('products.features'))
->schema([
Select::make('icon')
->label(__('products.feature_icon'))
->options($iconOptions)
->searchable()
->required(),
TextInput::make('title')
->label(__('products.feature_title'))
->required()
->columnSpanFull(),
Textarea::make('description')
->label(__('products.feature_description'))
->rows(2)
->columnSpanFull(),
])
->columns(1)
->defaultItems(0)
->addActionLabel(__('products.add_feature'))
->reorderable()
->collapsible()
->itemLabel(fn (array $state): ?string => $state['title'] ?? __('products.feature') . ' #' . ($state['_index'] ?? ''))
->columnSpanFull(),
// Note: Translations should match the order of features above
// Each feature translation array index should correspond to the feature index
])
->collapsible()
->collapsed(),
// How It Works Section
Section::make(__('products.how_it_works_section'))
->schema([
FileUpload::make('landing_page_data.how_it_works.download_image')
->label(__('products.how_it_works_image'))
->image()
->disk('public')
->directory('products/landing')
->columnSpanFull(),
TextInput::make('landing_page_data.how_it_works.download_form_label')
->label(__('products.download_form_label'))
->placeholder(__('products.download_form_label_placeholder'))
->columnSpanFull(),
// Steps (4 steps with translations)
Repeater::make('landing_page_data.how_it_works.steps')
->label(__('products.steps'))
->schema([
TextInput::make('number')
->label(__('products.step_number'))
->numeric()
->default(fn ($get) => ($get('../../../_index') ?? 0) + 1)
->required(),
TextInput::make('title')
->label(__('products.step_title'))
->required()
->columnSpanFull(),
Textarea::make('description')
->label(__('products.step_description'))
->rows(2)
->columnSpanFull(),
])
->columns(1)
->defaultItems(4)
->minItems(4)
->maxItems(4)
->reorderable(false)
->collapsible()
->itemLabel(fn (array $state): ?string => __('products.step') . ' ' . ($state['number'] ?? '') . ': ' . ($state['title'] ?? ''))
->columnSpanFull(),
// Note: Steps translations should match the order of steps above
// Each step translation array index should correspond to the step index
])
->collapsible()
->collapsed(),
// Video Section
Section::make(__('products.video_section'))
->schema([
TextInput::make('landing_page_data.video.youtube_video_id')
->label(__('products.youtube_video_id'))
->placeholder('165101721')
->helperText(__('products.youtube_video_id_helper'))
->columnSpanFull(),
])
->collapsible()
->collapsed(),
// FAQ Section
Section::make(__('products.faq_section'))
->schema([
Repeater::make('landing_page_data.faqs')
->label(__('products.faqs'))
->schema([
TextInput::make('question')
->label(__('products.faq_question'))
->required()
->columnSpanFull(),
Textarea::make('answer')
->label(__('products.faq_answer'))
->rows(3)
->required()
->columnSpanFull(),
])
->columns(1)
->defaultItems(0)
->addActionLabel(__('products.add_faq'))
->reorderable()
->collapsible()
->itemLabel(fn (array $state): ?string => $state['question'] ?? __('products.faq') . ' #' . ($state['_index'] ?? ''))
->columnSpanFull(),
// Note: FAQ translations should match the order of FAQs above
// Each FAQ translation array index should correspond to the FAQ index
])
->collapsible()
->collapsed(),
])
->visible(fn (Get $get) => $get('type') === 'product')
->columnSpanFull();
}
}
@@ -9,6 +9,7 @@ use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\FileUpload;
use Filament\Schemas\Components\Section;
use App\Filament\Admin\Resources\Components\TranslationTabs;
use App\Filament\Admin\Resources\Products\Schemas\LandingPageSection;
use App\Models\ProductCategory;
use Illuminate\Support\Str;
use Filament\Schemas\Components\Utilities\Get;
@@ -83,6 +84,9 @@ class ProductForm
],
]),
]),
// Landing Page Section (only for products)
LandingPageSection::make(),
]);
}
}
@@ -68,10 +68,17 @@ class ProductController extends Controller
'description' => \Str::limit(strip_tags($product->translate('content')), 160),
];
// Use custom view template if specified
if ($product->view_template && view()->exists($product->view_template)) {
return view($product->view_template, compact('product', 'settings', 'renderedHeader', 'renderedFooter', 'meta'));
}
// Use landing page template if landing_page_data exists and product type is 'product'
if ($product->type === 'product' && !empty($product->landing_page_data)) {
return view('front.products.landing', compact('product', 'settings', 'renderedHeader', 'renderedFooter', 'meta'));
}
// Default view
return view('front.products.show', compact('product', 'settings', 'renderedHeader', 'renderedFooter', 'meta'));
}
}
+2
View File
@@ -21,6 +21,7 @@ class Product extends Model
'view_template',
'sort_order',
'is_active',
'landing_page_data',
];
protected $translatable = [
@@ -32,6 +33,7 @@ class Product extends Model
'is_active' => 'boolean',
'title' => 'array',
'content' => 'array',
'landing_page_data' => 'array',
];
public static function boot()