diff --git a/app/Filament/Admin/Resources/FooterTemplates/FooterTemplateResource.php b/app/Filament/Admin/Resources/FooterTemplates/FooterTemplateResource.php
new file mode 100644
index 0000000..164bfb5
--- /dev/null
+++ b/app/Filament/Admin/Resources/FooterTemplates/FooterTemplateResource.php
@@ -0,0 +1,86 @@
+ ListFooterTemplates::route('/'),
+ 'create' => CreateFooterTemplate::route('/create'),
+ 'view' => ViewFooterTemplate::route('/{record}'),
+ 'edit' => EditFooterTemplate::route('/{record}/edit'),
+ ];
+ }
+
+ public static function getRecordRouteBindingEloquentQuery(): Builder
+ {
+ return parent::getRecordRouteBindingEloquentQuery()
+ ->withoutGlobalScopes([
+ SoftDeletingScope::class,
+ ]);
+ }
+}
diff --git a/app/Filament/Admin/Resources/FooterTemplates/Pages/CreateFooterTemplate.php b/app/Filament/Admin/Resources/FooterTemplates/Pages/CreateFooterTemplate.php
new file mode 100644
index 0000000..bb32ed0
--- /dev/null
+++ b/app/Filament/Admin/Resources/FooterTemplates/Pages/CreateFooterTemplate.php
@@ -0,0 +1,11 @@
+components([
+ Section::make(__('footer-templates.section_general'))
+ ->schema([
+ TextInput::make('title')
+ ->label(__('footer-templates.field_title'))
+ ->required()
+ ->maxLength(255)
+ ->columnSpanFull(),
+
+ CodeEditor::make('html_content')
+ ->label(__('footer-templates.field_html_content'))
+ ->required()
+ ->lineNumbers()
+ ->columnSpanFull()
+ ->helperText(__('footer-templates.field_html_content_help')),
+
+ Toggle::make('is_active')
+ ->label(__('footer-templates.field_is_active'))
+ ->default(true)
+ ->inline(false),
+ ])
+ ->columns(2),
+ ]);
+ }
+}
diff --git a/app/Filament/Admin/Resources/FooterTemplates/Schemas/FooterTemplateInfolist.php b/app/Filament/Admin/Resources/FooterTemplates/Schemas/FooterTemplateInfolist.php
new file mode 100644
index 0000000..05f6022
--- /dev/null
+++ b/app/Filament/Admin/Resources/FooterTemplates/Schemas/FooterTemplateInfolist.php
@@ -0,0 +1,16 @@
+components([
+ //
+ ]);
+ }
+}
diff --git a/app/Filament/Admin/Resources/FooterTemplates/Tables/FooterTemplatesTable.php b/app/Filament/Admin/Resources/FooterTemplates/Tables/FooterTemplatesTable.php
new file mode 100644
index 0000000..f7c68fc
--- /dev/null
+++ b/app/Filament/Admin/Resources/FooterTemplates/Tables/FooterTemplatesTable.php
@@ -0,0 +1,70 @@
+columns([
+ TextColumn::make('title')
+ ->label(__('footer-templates.column_title'))
+ ->searchable()
+ ->sortable(),
+
+ IconColumn::make('is_active')
+ ->label(__('footer-templates.column_is_active'))
+ ->boolean()
+ ->sortable(),
+
+ TextColumn::make('pages_count')
+ ->label(__('footer-templates.column_pages_count'))
+ ->counts('pages')
+ ->sortable(),
+
+ TextColumn::make('created_at')
+ ->label(__('footer-templates.column_created_at'))
+ ->dateTime()
+ ->sortable()
+ ->toggleable(isToggledHiddenByDefault: true),
+
+ TextColumn::make('updated_at')
+ ->label(__('footer-templates.column_updated_at'))
+ ->dateTime()
+ ->sortable()
+ ->toggleable(isToggledHiddenByDefault: true),
+
+ TextColumn::make('deleted_at')
+ ->label(__('footer-templates.column_deleted_at'))
+ ->dateTime()
+ ->sortable()
+ ->toggleable(isToggledHiddenByDefault: true),
+ ])
+ ->filters([
+ TrashedFilter::make(),
+ ])
+ ->recordActions([
+ ViewAction::make(),
+ EditAction::make(),
+ ])
+ ->toolbarActions([
+ BulkActionGroup::make([
+ DeleteBulkAction::make(),
+ ForceDeleteBulkAction::make(),
+ RestoreBulkAction::make(),
+ ]),
+ ]);
+ }
+}
diff --git a/app/Filament/Admin/Resources/HeaderTemplates/HeaderTemplateResource.php b/app/Filament/Admin/Resources/HeaderTemplates/HeaderTemplateResource.php
new file mode 100644
index 0000000..22918c4
--- /dev/null
+++ b/app/Filament/Admin/Resources/HeaderTemplates/HeaderTemplateResource.php
@@ -0,0 +1,86 @@
+ ListHeaderTemplates::route('/'),
+ 'create' => CreateHeaderTemplate::route('/create'),
+ 'view' => ViewHeaderTemplate::route('/{record}'),
+ 'edit' => EditHeaderTemplate::route('/{record}/edit'),
+ ];
+ }
+
+ public static function getRecordRouteBindingEloquentQuery(): Builder
+ {
+ return parent::getRecordRouteBindingEloquentQuery()
+ ->withoutGlobalScopes([
+ SoftDeletingScope::class,
+ ]);
+ }
+}
diff --git a/app/Filament/Admin/Resources/HeaderTemplates/Pages/CreateHeaderTemplate.php b/app/Filament/Admin/Resources/HeaderTemplates/Pages/CreateHeaderTemplate.php
new file mode 100644
index 0000000..1cbae6c
--- /dev/null
+++ b/app/Filament/Admin/Resources/HeaderTemplates/Pages/CreateHeaderTemplate.php
@@ -0,0 +1,11 @@
+components([
+ Section::make(__('header-templates.section_general'))
+ ->schema([
+ TextInput::make('title')
+ ->label(__('header-templates.field_title'))
+ ->required()
+ ->maxLength(255)
+ ->columnSpanFull(),
+
+ CodeEditor::make('html_content')
+ ->label(__('header-templates.field_html_content'))
+ ->required()
+ ->lineNumbers()
+ ->columnSpanFull()
+ ->helperText(__('header-templates.field_html_content_help')),
+
+ Toggle::make('is_active')
+ ->label(__('header-templates.field_is_active'))
+ ->default(true)
+ ->inline(false),
+ ])
+ ->columns(2),
+ ]);
+ }
+}
diff --git a/app/Filament/Admin/Resources/HeaderTemplates/Schemas/HeaderTemplateInfolist.php b/app/Filament/Admin/Resources/HeaderTemplates/Schemas/HeaderTemplateInfolist.php
new file mode 100644
index 0000000..c6334a4
--- /dev/null
+++ b/app/Filament/Admin/Resources/HeaderTemplates/Schemas/HeaderTemplateInfolist.php
@@ -0,0 +1,16 @@
+components([
+ //
+ ]);
+ }
+}
diff --git a/app/Filament/Admin/Resources/HeaderTemplates/Tables/HeaderTemplatesTable.php b/app/Filament/Admin/Resources/HeaderTemplates/Tables/HeaderTemplatesTable.php
new file mode 100644
index 0000000..0ae9b2f
--- /dev/null
+++ b/app/Filament/Admin/Resources/HeaderTemplates/Tables/HeaderTemplatesTable.php
@@ -0,0 +1,70 @@
+columns([
+ TextColumn::make('title')
+ ->label(__('header-templates.column_title'))
+ ->searchable()
+ ->sortable(),
+
+ IconColumn::make('is_active')
+ ->label(__('header-templates.column_is_active'))
+ ->boolean()
+ ->sortable(),
+
+ TextColumn::make('pages_count')
+ ->label(__('header-templates.column_pages_count'))
+ ->counts('pages')
+ ->sortable(),
+
+ TextColumn::make('created_at')
+ ->label(__('header-templates.column_created_at'))
+ ->dateTime()
+ ->sortable()
+ ->toggleable(isToggledHiddenByDefault: true),
+
+ TextColumn::make('updated_at')
+ ->label(__('header-templates.column_updated_at'))
+ ->dateTime()
+ ->sortable()
+ ->toggleable(isToggledHiddenByDefault: true),
+
+ TextColumn::make('deleted_at')
+ ->label(__('header-templates.column_deleted_at'))
+ ->dateTime()
+ ->sortable()
+ ->toggleable(isToggledHiddenByDefault: true),
+ ])
+ ->filters([
+ TrashedFilter::make(),
+ ])
+ ->recordActions([
+ ViewAction::make(),
+ EditAction::make(),
+ ])
+ ->toolbarActions([
+ BulkActionGroup::make([
+ DeleteBulkAction::make(),
+ ForceDeleteBulkAction::make(),
+ RestoreBulkAction::make(),
+ ]),
+ ]);
+ }
+}
diff --git a/app/Filament/Admin/Resources/Pages/Schemas/PageForm.php b/app/Filament/Admin/Resources/Pages/Schemas/PageForm.php
index b38f23f..e316ad9 100644
--- a/app/Filament/Admin/Resources/Pages/Schemas/PageForm.php
+++ b/app/Filament/Admin/Resources/Pages/Schemas/PageForm.php
@@ -3,6 +3,10 @@
namespace App\Filament\Admin\Resources\Pages\Schemas;
use App\Filament\Admin\Resources\Components\TranslationTabs;
+use App\Models\HeaderTemplate;
+use App\Models\SectionTemplate;
+use App\Models\FooterTemplate;
+use App\Services\TemplateService;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\CodeEditor;
use Filament\Forms\Components\CodeEditor\Enums\Language;
@@ -19,7 +23,9 @@ use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
+use Filament\Schemas\Components\Group;
use Filament\Schemas\Components\Utilities\Get;
+use Filament\Schemas\Components\Utilities\Set;
use Filament\Schemas\Schema;
class PageForm
@@ -527,6 +533,88 @@ class PageForm
->collapsible(true)
->collapsed(false),
+ // Dynamic Template System
+ Section::make(__('pages.form_section_header_template'))
+ ->description(__('pages.form_section_header_template_desc'))
+ ->schema([
+ Select::make('header_template_id')
+ ->label(__('pages.header_template_field'))
+ ->options(HeaderTemplate::where('is_active', true)->pluck('title', 'id'))
+ ->searchable()
+ ->live()
+ ->afterStateUpdated(fn (Set $set) => $set('header_data', []))
+ ->columnSpanFull(),
+
+ Group::make()
+ ->schema(fn (Get $get): array => TemplateService::generateDynamicFields(
+ HeaderTemplate::find($get('header_template_id')),
+ 'header_data'
+ ))
+ ->visible(fn (Get $get): bool => filled($get('header_template_id')))
+ ->columnSpanFull(),
+ ])
+ ->columnSpanFull()
+ ->collapsible(true)
+ ->collapsed(true),
+
+ Section::make(__('pages.form_section_template_sections'))
+ ->description(__('pages.form_section_template_sections_desc'))
+ ->schema([
+ Repeater::make('sections_data')
+ ->label(__('pages.template_sections_field'))
+ ->schema([
+ Select::make('section_template_id')
+ ->label(__('pages.section_template_field'))
+ ->options(SectionTemplate::where('is_active', true)->pluck('title', 'id'))
+ ->searchable()
+ ->live()
+ ->required()
+ ->columnSpanFull(),
+
+ Group::make()
+ ->schema(fn (Get $get): array => TemplateService::generateDynamicFields(
+ SectionTemplate::find($get('section_template_id')),
+ 'section_data'
+ ))
+ ->visible(fn (Get $get): bool => filled($get('section_template_id')))
+ ->columnSpanFull(),
+ ])
+ ->defaultItems(0)
+ ->addActionLabel(__('pages.add_template_section'))
+ ->reorderable()
+ ->collapsible()
+ ->itemLabel(fn (array $state): ?string =>
+ SectionTemplate::find($state['section_template_id'] ?? null)?->title ?? __('pages.template_section')
+ )
+ ->columnSpanFull(),
+ ])
+ ->columnSpanFull()
+ ->collapsible(true)
+ ->collapsed(true),
+
+ Section::make(__('pages.form_section_footer_template'))
+ ->description(__('pages.form_section_footer_template_desc'))
+ ->schema([
+ Select::make('footer_template_id')
+ ->label(__('pages.footer_template_field'))
+ ->options(FooterTemplate::where('is_active', true)->pluck('title', 'id'))
+ ->searchable()
+ ->live()
+ ->afterStateUpdated(fn (Set $set) => $set('footer_data', []))
+ ->columnSpanFull(),
+
+ Group::make()
+ ->schema(fn (Get $get): array => TemplateService::generateDynamicFields(
+ FooterTemplate::find($get('footer_template_id')),
+ 'footer_data'
+ ))
+ ->visible(fn (Get $get): bool => filled($get('footer_template_id')))
+ ->columnSpanFull(),
+ ])
+ ->columnSpanFull()
+ ->collapsible(true)
+ ->collapsed(true),
+
// Çeviri Sekmesi
Section::make('🌍 ' . __('pages.translations_section'))
->schema([
diff --git a/app/Filament/Admin/Resources/SectionTemplates/Pages/CreateSectionTemplate.php b/app/Filament/Admin/Resources/SectionTemplates/Pages/CreateSectionTemplate.php
new file mode 100644
index 0000000..6adc3d0
--- /dev/null
+++ b/app/Filament/Admin/Resources/SectionTemplates/Pages/CreateSectionTemplate.php
@@ -0,0 +1,11 @@
+components([
+ Section::make(__('section-templates.section_general'))
+ ->schema([
+ TextInput::make('title')
+ ->label(__('section-templates.field_title'))
+ ->required()
+ ->maxLength(255)
+ ->columnSpanFull(),
+
+ CodeEditor::make('html_content')
+ ->label(__('section-templates.field_html_content'))
+ ->required()
+ ->lineNumbers()
+ ->columnSpanFull()
+ ->helperText(__('section-templates.field_html_content_help')),
+
+ Toggle::make('is_active')
+ ->label(__('section-templates.field_is_active'))
+ ->default(true)
+ ->inline(false),
+ ])
+ ->columns(2),
+ ]);
+ }
+}
diff --git a/app/Filament/Admin/Resources/SectionTemplates/Schemas/SectionTemplateInfolist.php b/app/Filament/Admin/Resources/SectionTemplates/Schemas/SectionTemplateInfolist.php
new file mode 100644
index 0000000..d67f1f8
--- /dev/null
+++ b/app/Filament/Admin/Resources/SectionTemplates/Schemas/SectionTemplateInfolist.php
@@ -0,0 +1,16 @@
+components([
+ //
+ ]);
+ }
+}
diff --git a/app/Filament/Admin/Resources/SectionTemplates/SectionTemplateResource.php b/app/Filament/Admin/Resources/SectionTemplates/SectionTemplateResource.php
new file mode 100644
index 0000000..59ca77e
--- /dev/null
+++ b/app/Filament/Admin/Resources/SectionTemplates/SectionTemplateResource.php
@@ -0,0 +1,86 @@
+ ListSectionTemplates::route('/'),
+ 'create' => CreateSectionTemplate::route('/create'),
+ 'view' => ViewSectionTemplate::route('/{record}'),
+ 'edit' => EditSectionTemplate::route('/{record}/edit'),
+ ];
+ }
+
+ public static function getRecordRouteBindingEloquentQuery(): Builder
+ {
+ return parent::getRecordRouteBindingEloquentQuery()
+ ->withoutGlobalScopes([
+ SoftDeletingScope::class,
+ ]);
+ }
+}
diff --git a/app/Filament/Admin/Resources/SectionTemplates/Tables/SectionTemplatesTable.php b/app/Filament/Admin/Resources/SectionTemplates/Tables/SectionTemplatesTable.php
new file mode 100644
index 0000000..9e3f887
--- /dev/null
+++ b/app/Filament/Admin/Resources/SectionTemplates/Tables/SectionTemplatesTable.php
@@ -0,0 +1,65 @@
+columns([
+ TextColumn::make('title')
+ ->label(__('section-templates.column_title'))
+ ->searchable()
+ ->sortable(),
+
+ IconColumn::make('is_active')
+ ->label(__('section-templates.column_is_active'))
+ ->boolean()
+ ->sortable(),
+
+ TextColumn::make('created_at')
+ ->label(__('section-templates.column_created_at'))
+ ->dateTime()
+ ->sortable()
+ ->toggleable(isToggledHiddenByDefault: true),
+
+ TextColumn::make('updated_at')
+ ->label(__('section-templates.column_updated_at'))
+ ->dateTime()
+ ->sortable()
+ ->toggleable(isToggledHiddenByDefault: true),
+
+ TextColumn::make('deleted_at')
+ ->label(__('section-templates.column_deleted_at'))
+ ->dateTime()
+ ->sortable()
+ ->toggleable(isToggledHiddenByDefault: true),
+ ])
+ ->filters([
+ TrashedFilter::make(),
+ ])
+ ->recordActions([
+ ViewAction::make(),
+ EditAction::make(),
+ ])
+ ->toolbarActions([
+ BulkActionGroup::make([
+ DeleteBulkAction::make(),
+ ForceDeleteBulkAction::make(),
+ RestoreBulkAction::make(),
+ ]),
+ ]);
+ }
+}
diff --git a/app/Models/FooterTemplate.php b/app/Models/FooterTemplate.php
new file mode 100644
index 0000000..e52b8e1
--- /dev/null
+++ b/app/Models/FooterTemplate.php
@@ -0,0 +1,30 @@
+ 'boolean',
+ ];
+
+ /**
+ * Get the pages that use this footer template.
+ */
+ public function pages(): HasMany
+ {
+ return $this->hasMany(Page::class, 'footer_template_id');
+ }
+}
diff --git a/app/Models/HeaderTemplate.php b/app/Models/HeaderTemplate.php
new file mode 100644
index 0000000..6808b17
--- /dev/null
+++ b/app/Models/HeaderTemplate.php
@@ -0,0 +1,30 @@
+ 'boolean',
+ ];
+
+ /**
+ * Get the pages that use this header template.
+ */
+ public function pages(): HasMany
+ {
+ return $this->hasMany(Page::class, 'header_template_id');
+ }
+}
diff --git a/app/Models/Page.php b/app/Models/Page.php
index 38da609..e8a70f6 100644
--- a/app/Models/Page.php
+++ b/app/Models/Page.php
@@ -29,6 +29,12 @@ class Page extends Model
'show_in_menu',
'sections',
'data',
+ // Template System
+ 'header_template_id',
+ 'header_data',
+ 'footer_template_id',
+ 'footer_data',
+ 'sections_data',
];
/**
@@ -49,6 +55,10 @@ class Page extends Model
'sort_order' => 'integer',
'sections' => 'array',
'data' => 'array',
+ // Template System
+ 'header_data' => 'array',
+ 'footer_data' => 'array',
+ 'sections_data' => 'array',
];
public function author()
@@ -66,6 +76,22 @@ class Page extends Model
return $this->hasMany(Page::class, 'parent_id');
}
+ /**
+ * Get the header template for the page.
+ */
+ public function headerTemplate()
+ {
+ return $this->belongsTo(HeaderTemplate::class, 'header_template_id');
+ }
+
+ /**
+ * Get the footer template for the page.
+ */
+ public function footerTemplate()
+ {
+ return $this->belongsTo(FooterTemplate::class, 'footer_template_id');
+ }
+
public function getRouteKeyName()
{
return 'slug';
@@ -124,4 +150,25 @@ class Page extends Model
return $matchingSections[$index] ?? null;
}
+
+ /**
+ * Get templated sections with their templates loaded.
+ * Used for new dynamic template system.
+ */
+ public function getTemplatedSectionsAttribute()
+ {
+ if (!$this->sections_data || !is_array($this->sections_data)) {
+ return collect([]);
+ }
+
+ return collect($this->sections_data)->map(function ($section) {
+ $templateId = $section['section_template_id'] ?? null;
+ $template = $templateId ? SectionTemplate::find($templateId) : null;
+
+ return [
+ 'template' => $template,
+ 'data' => $section['section_data'] ?? [],
+ ];
+ })->filter(fn($section) => $section['template'] !== null);
+ }
}
diff --git a/app/Models/SectionTemplate.php b/app/Models/SectionTemplate.php
new file mode 100644
index 0000000..bfbd172
--- /dev/null
+++ b/app/Models/SectionTemplate.php
@@ -0,0 +1,30 @@
+ 'boolean',
+ ];
+
+ /**
+ * Section templates are used via JSON in pages.sections_data
+ * No direct relation needed, but we can add a helper method
+ */
+ public function getPagesUsingThisTemplate()
+ {
+ return Page::whereJsonContains('sections_data', [['section_template_id' => $this->id]])->get();
+ }
+}
diff --git a/app/Services/TemplateService.php b/app/Services/TemplateService.php
new file mode 100644
index 0000000..65ab39b
--- /dev/null
+++ b/app/Services/TemplateService.php
@@ -0,0 +1,259 @@
+html_content);
+ $fields = [];
+
+ foreach ($placeholders as $placeholder) {
+ $parts = explode('.', $placeholder);
+
+ if (count($parts) !== 2) {
+ continue; // Skip invalid placeholders
+ }
+
+ [$type, $name] = $parts;
+
+ $label = str($name)->title()->replace('_', ' ')->toString();
+
+ $field = match($type) {
+ // Text Input Variants
+ 'text' => TextInput::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->maxLength(255),
+
+ 'email' => TextInput::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->email()
+ ->maxLength(255),
+
+ 'url' => TextInput::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->url()
+ ->maxLength(500),
+
+ 'tel' => TextInput::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->tel()
+ ->maxLength(20),
+
+ 'number' => TextInput::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->numeric(),
+
+ 'password' => TextInput::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->password()
+ ->dehydrated(fn ($state) => filled($state))
+ ->maxLength(255),
+
+ // Text Area & Editors
+ 'textarea' => Textarea::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->rows(4)
+ ->maxLength(5000),
+
+ 'richtext' => RichEditor::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->toolbarButtons([
+ 'bold', 'italic', 'underline', 'link',
+ 'bulletList', 'orderedList', 'h2', 'h3',
+ ])
+ ->maxLength(50000),
+
+ 'markdown' => MarkdownEditor::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->toolbarButtons([
+ 'bold', 'italic', 'strike', 'link',
+ 'heading', 'bulletList', 'orderedList', 'codeBlock',
+ ])
+ ->maxLength(50000),
+
+ 'code' => CodeEditor::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->lineNumbers()
+ ->maxLength(50000),
+
+ // Date & Time
+ 'date' => DatePicker::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->native(false),
+
+ 'datetime' => DateTimePicker::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->native(false)
+ ->seconds(false),
+
+ 'time' => TimePicker::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->native(false)
+ ->seconds(false),
+
+ // File Uploads
+ 'image' => FileUpload::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->image()
+ ->imageEditor()
+ ->imageEditorAspectRatios([null, '16:9', '4:3', '1:1'])
+ ->directory('templates/images')
+ ->maxSize(5120),
+
+ 'images' => FileUpload::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->image()
+ ->multiple()
+ ->imageEditor()
+ ->directory('templates/images')
+ ->maxSize(5120)
+ ->maxFiles(10),
+
+ 'file' => FileUpload::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->directory('templates/files')
+ ->maxSize(10240),
+
+ 'files' => FileUpload::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->multiple()
+ ->directory('templates/files')
+ ->maxSize(10240)
+ ->maxFiles(10),
+
+ // Selection & Options
+ 'select' => Select::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->options(self::getSelectOptions($name))
+ ->searchable(),
+
+ 'multiselect' => Select::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->multiple()
+ ->options(self::getSelectOptions($name))
+ ->searchable(),
+
+ 'checkbox' => Checkbox::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->inline(false),
+
+ 'checkboxlist' => CheckboxList::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->options(self::getSelectOptions($name))
+ ->columns(2),
+
+ 'radio' => Radio::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->options(self::getSelectOptions($name))
+ ->inline()
+ ->inlineLabel(false),
+
+ 'toggle' => Toggle::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->inline(false),
+
+ 'togglebuttons' => ToggleButtons::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->options(self::getSelectOptions($name))
+ ->inline()
+ ->grouped(),
+
+ // Color & Visual
+ 'color' => ColorPicker::make("{$dataKey}.{$placeholder}")
+ ->label($label),
+
+ // Structured Data
+ 'tags' => TagsInput::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->separator(','),
+
+ 'keyvalue' => KeyValue::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->keyLabel('Key')
+ ->valueLabel('Value')
+ ->reorderable(),
+
+ // Advanced
+ 'slider' => Slider::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->minValue(0)
+ ->maxValue(100)
+ ->step(1),
+
+ 'hidden' => Hidden::make("{$dataKey}.{$placeholder}"),
+
+ // Default
+ default => TextInput::make("{$dataKey}.{$placeholder}")
+ ->label($label)
+ ->maxLength(255)
+ ->helperText("Unknown type: {$type}"),
+ };
+
+ $fields[] = $field;
+ }
+
+ return $fields;
+ }
+
+ /**
+ * Parse placeholders from HTML content
+ * Format: {type.field_name}
+ */
+ public static function parsePlaceholders(string $html): array
+ {
+ preg_match_all('/\{([a-z]+\.[a-z_]+)\}/i', $html, $matches);
+ return array_unique($matches[1] ?? []);
+ }
+
+ /**
+ * Get select options for a field
+ * Can be extended to load from database or config
+ */
+ protected static function getSelectOptions(string $fieldName): array
+ {
+ return config("template-options.{$fieldName}", [
+ 'option_1' => __('Option 1'),
+ 'option_2' => __('Option 2'),
+ 'option_3' => __('Option 3'),
+ ]);
+ }
+
+ /**
+ * Replace placeholders in HTML with actual data
+ */
+ public static function replacePlaceholders(string $html, array $data): string
+ {
+ foreach ($data as $placeholder => $value) {
+ // Handle different value types
+ if (is_array($value)) {
+ $value = implode(', ', $value);
+ } elseif (is_bool($value)) {
+ $value = $value ? 'true' : 'false';
+ }
+
+ $html = str_replace("{{$placeholder}}", $value ?? '', $html);
+ }
+
+ return $html;
+ }
+}
+
diff --git a/app/View/Components/PageRenderer.php b/app/View/Components/PageRenderer.php
new file mode 100644
index 0000000..98a5192
--- /dev/null
+++ b/app/View/Components/PageRenderer.php
@@ -0,0 +1,81 @@
+page = $page;
+ }
+
+ /**
+ * Get the view / contents that represent the component.
+ */
+ public function render(): View|Closure|string
+ {
+ return view('components.page-renderer');
+ }
+
+ /**
+ * Render the header template with data
+ */
+ public function renderHeader(): string
+ {
+ if (!$this->page->headerTemplate) {
+ return '';
+ }
+
+ return TemplateService::replacePlaceholders(
+ $this->page->headerTemplate->html_content,
+ $this->page->header_data ?? []
+ );
+ }
+
+ /**
+ * Render all sections with their templates
+ */
+ public function renderSections(): string
+ {
+ $output = '';
+
+ foreach ($this->page->templated_sections as $section) {
+ if (!$section['template']) {
+ continue;
+ }
+
+ $output .= TemplateService::replacePlaceholders(
+ $section['template']->html_content,
+ $section['data']
+ );
+ }
+
+ return $output;
+ }
+
+ /**
+ * Render the footer template with data
+ */
+ public function renderFooter(): string
+ {
+ if (!$this->page->footerTemplate) {
+ return '';
+ }
+
+ return TemplateService::replacePlaceholders(
+ $this->page->footerTemplate->html_content,
+ $this->page->footer_data ?? []
+ );
+ }
+}
diff --git a/config/template-options.php b/config/template-options.php
new file mode 100644
index 0000000..f3b8095
--- /dev/null
+++ b/config/template-options.php
@@ -0,0 +1,43 @@
+ ['key' => 'Label']
+ |
+ */
+
+ // Example options - Add your own as needed
+ 'example_select' => [
+ 'option_1' => 'Option 1',
+ 'option_2' => 'Option 2',
+ 'option_3' => 'Option 3',
+ ],
+
+ 'status' => [
+ 'active' => 'Active',
+ 'inactive' => 'Inactive',
+ 'pending' => 'Pending',
+ ],
+
+ 'size' => [
+ 'small' => 'Small',
+ 'medium' => 'Medium',
+ 'large' => 'Large',
+ ],
+
+ 'color_scheme' => [
+ 'light' => 'Light',
+ 'dark' => 'Dark',
+ 'auto' => 'Auto',
+ ],
+
+ // Add more options as needed
+];
+
diff --git a/database/migrations/2025_10_31_173856_create_header_templates_table.php b/database/migrations/2025_10_31_173856_create_header_templates_table.php
new file mode 100644
index 0000000..b5ba3f6
--- /dev/null
+++ b/database/migrations/2025_10_31_173856_create_header_templates_table.php
@@ -0,0 +1,31 @@
+id();
+ $table->string('title');
+ $table->longText('html_content');
+ $table->boolean('is_active')->default(true);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('header_templates');
+ }
+};
diff --git a/database/migrations/2025_10_31_173857_create_section_templates_table.php b/database/migrations/2025_10_31_173857_create_section_templates_table.php
new file mode 100644
index 0000000..58f7703
--- /dev/null
+++ b/database/migrations/2025_10_31_173857_create_section_templates_table.php
@@ -0,0 +1,31 @@
+id();
+ $table->string('title');
+ $table->longText('html_content');
+ $table->boolean('is_active')->default(true);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('section_templates');
+ }
+};
diff --git a/database/migrations/2025_10_31_173858_create_footer_templates_table.php b/database/migrations/2025_10_31_173858_create_footer_templates_table.php
new file mode 100644
index 0000000..be8e7d2
--- /dev/null
+++ b/database/migrations/2025_10_31_173858_create_footer_templates_table.php
@@ -0,0 +1,31 @@
+id();
+ $table->string('title');
+ $table->longText('html_content');
+ $table->boolean('is_active')->default(true);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('footer_templates');
+ }
+};
diff --git a/database/migrations/2025_10_31_173859_add_template_fields_to_pages_table.php b/database/migrations/2025_10_31_173859_add_template_fields_to_pages_table.php
new file mode 100644
index 0000000..ea9b394
--- /dev/null
+++ b/database/migrations/2025_10_31_173859_add_template_fields_to_pages_table.php
@@ -0,0 +1,42 @@
+foreignId('header_template_id')->nullable()->after('id')->constrained('header_templates')->nullOnDelete();
+ $table->json('header_data')->nullable()->after('header_template_id');
+
+ $table->foreignId('footer_template_id')->nullable()->after('header_data')->constrained('footer_templates')->nullOnDelete();
+ $table->json('footer_data')->nullable()->after('footer_template_id');
+
+ $table->json('sections_data')->nullable()->after('footer_data');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('pages', function (Blueprint $table) {
+ $table->dropForeign(['header_template_id']);
+ $table->dropColumn('header_template_id');
+ $table->dropColumn('header_data');
+
+ $table->dropForeign(['footer_template_id']);
+ $table->dropColumn('footer_template_id');
+ $table->dropColumn('footer_data');
+
+ $table->dropColumn('sections_data');
+ });
+ }
+};
diff --git a/docs/DYNAMIC_TEMPLATE_SYSTEM.md b/docs/DYNAMIC_TEMPLATE_SYSTEM.md
new file mode 100644
index 0000000..d6a98c8
--- /dev/null
+++ b/docs/DYNAMIC_TEMPLATE_SYSTEM.md
@@ -0,0 +1,796 @@
+# Dynamic Template System (Dinamik Şablon Sistemi)
+
+## Genel Bakış
+
+Bu sistem, sayfalara dinamik header, section ve footer şablonları ekleyerek içerik yönetimini tamamen özelleştirilebilir hale getirir.
+
+## Mimari
+
+### 1. Template Modülleri
+
+#### 1.1 Header Template
+- **Tablo:** `header_templates`
+- **Alanlar:**
+ - `id` (bigint, PK)
+ - `title` (string, 255) - Şablon başlığı
+ - `html_content` (longtext) - HTML içerik + placeholder'lar ✅ **4GB kapasiteli**
+ - `is_active` (boolean) - Aktif/Pasif
+ - `created_at`, `updated_at`, `deleted_at`
+
+#### 1.2 Section Template
+- **Tablo:** `section_templates`
+- **Alanlar:**
+ - `id` (bigint, PK)
+ - `title` (string, 255) - Şablon başlığı
+ - `html_content` (longtext) - HTML içerik + placeholder'lar ✅ **4GB kapasiteli**
+ - `is_active` (boolean) - Aktif/Pasif
+ - `created_at`, `updated_at`, `deleted_at`
+
+#### 1.3 Footer Template
+- **Tablo:** `footer_templates`
+- **Alanlar:**
+ - `id` (bigint, PK)
+ - `title` (string, 255) - Şablon başlığı
+ - `html_content` (longtext) - HTML içerik + placeholder'lar ✅ **4GB kapasiteli**
+ - `is_active` (boolean) - Aktif/Pasif
+ - `created_at`, `updated_at`, `deleted_at`
+
+**Not:** `longtext` veri tipi MySQL'de **4,294,967,295 karakter** (4GB) kapasiteye sahiptir. Bu, en karmaşık HTML template'ler ve zengin içerikler için fazlasıyla yeterlidir.
+
+### 2. Placeholder Sistemi
+
+#### 2.1 Placeholder Formatı
+```
+{form_type.field_name}
+```
+
+**Desteklenen Form Tipleri:**
+
+#### Text Input Variants:
+- `text` → TextInput (basic text)
+- `email` → TextInput (email validation)
+- `url` → TextInput (URL validation)
+- `tel` → TextInput (telephone)
+- `number` → TextInput (numeric)
+- `password` → TextInput (password)
+
+#### Text Area & Editors:
+- `textarea` → Textarea (multi-line text)
+- `richtext` → RichEditor (WYSIWYG HTML editor)
+- `markdown` → MarkdownEditor (Markdown editor with preview)
+- `code` → CodeEditor (syntax highlighted code)
+
+#### Date & Time:
+- `date` → DatePicker (date only)
+- `datetime` → DateTimePicker (date + time)
+- `time` → TimePicker (time only)
+
+#### File Uploads:
+- `image` → FileUpload (image only, with preview)
+- `file` → FileUpload (any file type)
+- `files` → FileUpload (multiple files)
+- `images` → FileUpload (multiple images)
+
+#### Selection & Options:
+- `select` → Select (dropdown selection)
+- `multiselect` → Select (multiple selection)
+- `checkbox` → Checkbox (single checkbox)
+- `checkboxlist` → CheckboxList (multiple checkboxes)
+- `radio` → Radio (radio buttons)
+- `toggle` → Toggle (switch button)
+- `togglebuttons` → ToggleButtons (button group)
+
+#### Color & Visual:
+- `color` → ColorPicker (color picker)
+
+#### Structured Data:
+- `tags` → TagsInput (tag input)
+- `keyvalue` → KeyValue (key-value pairs)
+- `repeater` → Repeater (repeatable fields - not recommended in template)
+- `builder` → Builder (block builder - not recommended in template)
+
+#### Advanced:
+- `slider` → Slider (range slider)
+- `hidden` → Hidden (hidden field for calculations)
+
+**Toplam: 30+ Form Tipi Desteği**
+
+#### Form Tipi Referans Tablosu
+
+| Placeholder Type | Filament Component | Örnek Kullanım | Veri Tipi | Max Boyut |
+|-----------------|-------------------|----------------|-----------|-----------|
+| `{text.name}` | TextInput | Kısa metinler | string | 255 char |
+| `{email.address}` | TextInput (email) | E-posta adresleri | string | 255 char |
+| `{url.link}` | TextInput (url) | Web adresleri | string | 500 char |
+| `{tel.phone}` | TextInput (tel) | Telefon numaraları | string | 20 char |
+| `{number.count}` | TextInput (numeric) | Sayısal değerler | integer/float | - |
+| `{password.pass}` | TextInput (password) | Şifreler | string (hashed) | 255 char |
+| `{textarea.desc}` | Textarea | Çok satırlı metin | string | 5000 char |
+| `{richtext.content}` | RichEditor | HTML içerik | longtext | 50000 char |
+| `{markdown.post}` | MarkdownEditor | Markdown içerik | longtext | 50000 char |
+| `{code.snippet}` | CodeEditor | Kod blokları | longtext | 50000 char |
+| `{date.birthday}` | DatePicker | Tarih seçimi | date | - |
+| `{datetime.event}` | DateTimePicker | Tarih + saat | datetime | - |
+| `{time.opening}` | TimePicker | Saat seçimi | time | - |
+| `{image.banner}` | FileUpload | Tek resim | string (path) | 5MB |
+| `{images.gallery}` | FileUpload (multiple) | Çoklu resim | json (paths) | 10x5MB |
+| `{file.document}` | FileUpload | Tek dosya | string (path) | 10MB |
+| `{files.attachments}` | FileUpload (multiple) | Çoklu dosya | json (paths) | 10x10MB |
+| `{select.category}` | Select | Tekli seçim | string/int | - |
+| `{multiselect.tags}` | Select (multiple) | Çoklu seçim | json (array) | - |
+| `{checkbox.agree}` | Checkbox | Tekli checkbox | boolean | - |
+| `{checkboxlist.options}` | CheckboxList | Checkbox listesi | json (array) | - |
+| `{radio.gender}` | Radio | Radio button | string/int | - |
+| `{toggle.active}` | Toggle | Açık/Kapalı | boolean | - |
+| `{togglebuttons.size}` | ToggleButtons | Buton grubu | string | - |
+| `{color.theme}` | ColorPicker | Renk seçici | string (hex) | 7 char |
+| `{tags.keywords}` | TagsInput | Etiket girişi | json (array) | - |
+| `{keyvalue.meta}` | KeyValue | Anahtar-değer | json (object) | - |
+| `{slider.volume}` | Slider | Kaydırıcı | integer | - |
+| `{hidden.calc}` | Hidden | Gizli alan | mixed | - |
+
+#### 2.2 Örnek Template (Comprehensive)
+```html
+
+ {textarea.tagline}
+
+
+ info@company.com
+