diff --git a/app/Filament/Admin/Resources/Products/Schemas/LandingPageSection.php b/app/Filament/Admin/Resources/Products/Schemas/LandingPageSection.php new file mode 100644 index 0000000..e8c587d --- /dev/null +++ b/app/Filament/Admin/Resources/Products/Schemas/LandingPageSection.php @@ -0,0 +1,228 @@ + 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(); + } +} + diff --git a/app/Filament/Admin/Resources/Products/Schemas/ProductForm.php b/app/Filament/Admin/Resources/Products/Schemas/ProductForm.php index 8f4fe3f..5ceeb1c 100644 --- a/app/Filament/Admin/Resources/Products/Schemas/ProductForm.php +++ b/app/Filament/Admin/Resources/Products/Schemas/ProductForm.php @@ -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(), ]); } } diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index f878c34..bfe8e5d 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -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')); } } diff --git a/app/Models/Product.php b/app/Models/Product.php index 84e2de6..e6a1ed8 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -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() diff --git a/database/migrations/2026_01_02_115025_add_landing_page_data_to_products_table.php b/database/migrations/2026_01_02_115025_add_landing_page_data_to_products_table.php new file mode 100644 index 0000000..556d3cd --- /dev/null +++ b/database/migrations/2026_01_02_115025_add_landing_page_data_to_products_table.php @@ -0,0 +1,28 @@ +json('landing_page_data')->nullable()->after('content'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('products', function (Blueprint $table) { + $table->dropColumn('landing_page_data'); + }); + } +}; diff --git a/lang/en/products.php b/lang/en/products.php index 11b4d2b..715ed27 100644 --- a/lang/en/products.php +++ b/lang/en/products.php @@ -25,6 +25,43 @@ return [ // Table 'title' => 'Title', 'created_at' => 'Created At', + + // Landing Page + 'landing_page_section' => 'Landing Page Template', + 'hero_section' => 'Hero Section', + 'hero_slogan' => 'Main Slogan', + 'hero_sub_slogan' => 'Sub Slogan', + 'app_store_link' => 'App Store Link', + 'google_play_link' => 'Google Play Link', + 'hero_featured_image' => 'Featured Image', + 'hero_background_image' => 'Hero Background Image', + 'hero_background_image_helper' => 'Select the background image for the hero section', + 'select_background' => 'Select background...', + 'app_features_section' => 'App Features', + 'features' => 'Features', + 'add_feature' => 'Add Feature', + 'feature' => 'Feature', + 'feature_icon' => 'Icon', + 'feature_title' => 'Feature Title', + 'feature_description' => 'Feature Description', + 'how_it_works_section' => 'How It Works', + 'how_it_works_image' => 'Image', + 'download_form_label' => 'Download Form Label', + 'download_form_label_placeholder' => 'Download the app', + 'steps' => 'Steps', + 'step' => 'Step', + 'step_number' => 'Step Number', + 'step_title' => 'Step Title', + 'step_description' => 'Step Description', + 'video_section' => 'Video', + 'youtube_video_id' => 'YouTube Video ID', + 'youtube_video_id_helper' => 'The ID from YouTube video URL (e.g.: 165101721)', + 'faq_section' => 'Frequently Asked Questions', + 'faqs' => 'FAQs', + 'add_faq' => 'Add FAQ', + 'faq' => 'FAQ', + 'faq_question' => 'Question', + 'faq_answer' => 'Answer', ]; diff --git a/lang/tr/products.php b/lang/tr/products.php index 3bcda55..c004e19 100644 --- a/lang/tr/products.php +++ b/lang/tr/products.php @@ -25,6 +25,43 @@ return [ // Table 'title' => 'Başlık', 'created_at' => 'Oluşturulma Tarihi', + + // Landing Page + 'landing_page_section' => 'Landing Page Şablonu', + 'hero_section' => 'Hero Bölümü', + 'hero_slogan' => 'Ana Slogan', + 'hero_sub_slogan' => 'Alt Slogan', + 'app_store_link' => 'App Store Linki', + 'google_play_link' => 'Google Play Linki', + 'hero_featured_image' => 'Öne Çıkan Görsel', + 'hero_background_image' => 'Hero Arka Plan Görseli', + 'hero_background_image_helper' => 'Hero section için kullanılacak arka plan görselini seçin', + 'select_background' => 'Arka plan seçin...', + 'app_features_section' => 'Uygulama Özellikleri', + 'features' => 'Özellikler', + 'add_feature' => 'Özellik Ekle', + 'feature' => 'Özellik', + 'feature_icon' => 'İkon', + 'feature_title' => 'Özellik Başlığı', + 'feature_description' => 'Özellik Açıklaması', + 'how_it_works_section' => 'Nasıl Çalışır', + 'how_it_works_image' => 'Görsel', + 'download_form_label' => 'Download Form Etiketi', + 'download_form_label_placeholder' => 'Download the app', + 'steps' => 'Adımlar', + 'step' => 'Adım', + 'step_number' => 'Adım Numarası', + 'step_title' => 'Adım Başlığı', + 'step_description' => 'Adım Açıklaması', + 'video_section' => 'Video', + 'youtube_video_id' => 'YouTube Video ID', + 'youtube_video_id_helper' => 'YouTube video URL\'sindeki ID (örn: 165101721)', + 'faq_section' => 'Sık Sorulan Sorular', + 'faqs' => 'SSS', + 'add_faq' => 'SSS Ekle', + 'faq' => 'SSS', + 'faq_question' => 'Soru', + 'faq_answer' => 'Cevap', ]; diff --git a/resources/views/front/products/landing.blade.php b/resources/views/front/products/landing.blade.php new file mode 100644 index 0000000..9dad5c4 --- /dev/null +++ b/resources/views/front/products/landing.blade.php @@ -0,0 +1,295 @@ + +@extends('layouts.site', ['headerTemplate' => 'Demo29 Header']) + +@php +use Illuminate\Support\Facades\Storage; +@endphp + +@section('title', $product->translate('title') ?? 'Ürün Detayı') +@section('meta_description', Str::limit(strip_tags($product->translate('content')), 160)) +@php + $landingData = $product->landing_page_data ?? []; + $hero = $landingData['hero'] ?? []; + $features = $landingData['features'] ?? []; + $howItWorks = $landingData['how_it_works'] ?? []; + $steps = $howItWorks['steps'] ?? []; + $video = $landingData['video'] ?? []; + $faqs = $landingData['faqs'] ?? []; + $currentLang = app()->getLocale(); + + // Helper function to get translated value with fallback + $getTranslated = function($item, $key, $default = '') { + global $currentLang; + if (isset($item["{$key}_{$currentLang}"])) { + return $item["{$key}_{$currentLang}"]; + } + return $item[$key] ?? $default; + }; +@endphp + +@section('content') + {{-- Hero Section --}} + @if(!empty($hero)) +
+
+
+
+ @if(!empty($hero['slogan'])) +

+ {!! nl2br(e($hero['slogan'])) !!} +

+ @endif + @if(!empty($hero['sub_slogan'])) +

{{ $hero['sub_slogan'] }}

+ @endif +
+ @if(!empty($hero['app_store_link'])) + + App Store + + @endif + @if(!empty($hero['google_play_link'])) + + Google Play + + @endif +
+
+ + @if(!empty($hero['featured_image'])) +
+
+ {{ $product->translate('title') }} +
+
+ + @endif +
+ +
+ +
+
+ + + + + +
+
+ +
+ + @endif + + {{-- App Features Section --}} + @if(!empty($features)) +
+
+
+
+

App Features

+

+ {{ $product->translate('title') }} makes your experience better for you to have the perfect control. +

+
+ +
+ +
+
+
+ @foreach($features as $index => $feature) +
+ @if(!empty($feature['icon'])) +
+ @php + $iconPath = public_path('docs/styleguide/_/assets/img/icons/solid/' . $feature['icon'] . '.svg'); + $iconUrl = file_exists($iconPath) + ? asset('docs/styleguide/_/assets/img/icons/solid/' . $feature['icon'] . '.svg') + : null; + @endphp + @if($iconUrl) + {{ $feature['title'] ?? '' }} + @else +
+ {{ substr($feature['icon'], 0, 2) }} +
+ @endif +
+ @endif + @if(!empty($feature['title'])) +

{{ $feature['title'] }}

+ @endif +
+ + @endforeach +
+ +
+ +
+ +
+ +
+ + @endif + + {{-- How It Works Section --}} + @if(!empty($howItWorks) && !empty($steps)) +
+
+
+
+

How It Works

+ @if(!empty($howItWorks['download_form_label'])) +

+ {{ $howItWorks['download_form_label'] }} +

+ @endif +
+ +
+ +
+
+
+ @if(!empty($howItWorks['download_image'])) +
+
+ How It Works +
+
+ + @endif +
+
+ @foreach($steps as $index => $step) + @if($index < 2) +
+ {{ str_pad($step['number'] ?? ($index + 1), 2, '0', STR_PAD_LEFT) }} + @if(!empty($step['title'])) +

{{ $step['title'] }}

+ @endif + @if(!empty($step['description'])) +

{{ $step['description'] }}

+ @endif +
+ + @endif + @endforeach +
+ +
+ @foreach($steps as $index => $step) + @if($index >= 2) +
+ {{ str_pad($step['number'] ?? ($index + 1), 2, '0', STR_PAD_LEFT) }} + @if(!empty($step['title'])) +

{{ $step['title'] }}

+ @endif + @if(!empty($step['description'])) +

{{ $step['description'] }}

+ @endif +
+ + @endif + @endforeach +
+ +
+ +
+ +
+ +
+ +
+ + @endif + + {{-- Video Section --}} + @if(!empty($video['youtube_video_id'])) +
+
+
+
+
+
+ +
+
+
+ +
+ +
+ +
+ + @endif + + {{-- FAQ Section --}} + @if(!empty($faqs)) +
+
+
+
+
+

FAQ

+

+ If you don't see an answer to your question, you can send us an email from our contact form. +

+
+
+
+
+ @foreach($faqs as $index => $faq) +
+
+ +
+ +
+
+

{!! nl2br(e($faq['answer'] ?? '')) !!}

+
+ +
+ +
+ + @endforeach +
+ +
+ +
+ +
+ +
+ +
+ +
+ + @endif +@endsection + diff --git a/resources/views/layouts/site.blade.php b/resources/views/layouts/site.blade.php index b561b56..7163d18 100644 --- a/resources/views/layouts/site.blade.php +++ b/resources/views/layouts/site.blade.php @@ -16,6 +16,7 @@ $favicon_path = setting('site_favicon'); +