Refactor form components across multiple pages: enforce required validation on various text inputs and file uploads in HomePageSettings, ManagePortfolio, ManageSite, ManageSiteSocialSettings, ManageSolutions, ManageSuccess, and update the news index view to display dynamic content.

This commit is contained in:
2025-07-28 17:14:40 +05:00
parent 1ceccb0d79
commit bae4204d44
29 changed files with 360 additions and 50 deletions

View File

@@ -3,14 +3,13 @@
namespace App\Filament\Pages;
use App\Settings\HomeSettings;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Pages\Page;
use Filament\Pages\SettingsPage;
use Illuminate\Contracts\Support\Htmlable;
@@ -81,19 +80,19 @@ class HomePageSettings extends SettingsPage
->label('Projects Number')
->numeric()
->required(),
])->columns(2),
Grid::make()->schema([
TextInput::make('about_members_text')
->label('Members Text')
->maxLength(100)
->required(),
TextInput::make('about_members_number')
->label('Members Number')
->numeric()
->required(),
])->columns(2),
Grid::make()->schema([
@@ -106,7 +105,7 @@ class HomePageSettings extends SettingsPage
->label('Reviews Number')
->numeric()
->required(),
])->columns(2),
Grid::make()->schema([
TextInput::make('about_button_text')
@@ -148,7 +147,7 @@ class HomePageSettings extends SettingsPage
->url()
->required(),
]),
Section::make('Industry Area')
->description('Manage the content for the industry area section.')
->icon('heroicon-o-building-office')

View File

@@ -3,11 +3,11 @@
namespace App\Filament\Pages;
use App\Settings\PortfolioSettings;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Form;
use Filament\Pages\SettingsPage;
use Illuminate\Contracts\Support\Htmlable;
@@ -33,9 +33,11 @@ class ManagePortfolio extends SettingsPage
Grid::make()->schema([
TextInput::make('portfolio_button_text')
->label('Button Text')
->required()
->maxLength(50),
TextInput::make('portfolio_button_url')
->label('Button URL')
->required()
->maxLength(255)
->url(),
])->columns(2),
@@ -49,7 +51,8 @@ class ManagePortfolio extends SettingsPage
->schema([
FileUpload::make('image')
->label('Image (700x525)')
->image() ->maxSize(2048)
->image()
->maxSize(2048)
->disk('public')
->directory('portfolio-images')
->required(),
@@ -103,4 +106,4 @@ class ManagePortfolio extends SettingsPage
{
return 'Manage the portfolio section content, including items, categories, and titles.';
}
}
}

View File

@@ -38,10 +38,12 @@ class ManageSite extends SettingsPage
->maxLength(100),
Forms\Components\TextInput::make('tagline')
->label('Site Tagline')
->required()
->helperText('A short phrase describing your site')
->maxLength(150),
Forms\Components\Textarea::make('description')
->label('Site Description')
->required()
->helperText('A detailed description of your website')
->rows(3)
->maxLength(500),
@@ -65,13 +67,16 @@ class ManageSite extends SettingsPage
->maxLength(100),
Forms\Components\TextInput::make('company_phone')
->label('Company Phone')
->required()
->maxLength(20),
Forms\Components\TextInput::make('company_phone_2')
->label('Company Additional Phone')
->required()
->maxLength(20),
])->columns(2),
Forms\Components\Textarea::make('company_address')
->label('Company Address')
->required()
->rows(2)
->maxLength(200),
]),
@@ -94,17 +99,20 @@ class ManageSite extends SettingsPage
->schema([
Forms\Components\TextInput::make('copyright_text')
->label('Copyright Text')
->required()
->maxLength(200),
Forms\Components\Grid::make()->schema([
Forms\Components\TextInput::make('terms_url')
->label('Terms & Conditions URL')
->required()
->maxLength(100)
->prefix(function (Forms\Get $get) {
return url('/');
}),
Forms\Components\TextInput::make('privacy_url')
->label('Privacy Policy URL')
->required()
->maxLength(100)
->prefix(function (Forms\Get $get) {
return url('/');
@@ -120,10 +128,12 @@ class ManageSite extends SettingsPage
Forms\Components\Grid::make()->schema([
Forms\Components\Textarea::make('custom_404_message')
->label('404 Not Found Message')
->required()
->rows(2)
->maxLength(500),
Forms\Components\Textarea::make('custom_500_message')
->label('500 Server Error Message')
->required()
->rows(2)
->maxLength(500),
])->columns(2),

View File

@@ -28,18 +28,22 @@ class ManageSiteSocialSettings extends SettingsPage
Forms\Components\Grid::make()->schema([
Forms\Components\TextInput::make('facebook_url')
->label('Facebook URL')
->required()
->prefix('https://')
->helperText('e.g., facebook.com/yourpage'),
Forms\Components\TextInput::make('twitter_url')
->label('Twitter/X URL')
->required()
->prefix('https://')
->helperText('e.g., twitter.com/yourusername'),
Forms\Components\TextInput::make('instagram_url')
->label('Instagram URL')
->required()
->prefix('https://')
->helperText('e.g., instagram.com/yourusername'),
Forms\Components\TextInput::make('linkedin_url')
->label('LinkedIn URL')
->required()
->prefix('https://')
->helperText('e.g., linkedin.com/company/yourcompany'),
])->columns(2),

View File

@@ -4,12 +4,10 @@ namespace App\Filament\Pages;
use App\Settings\SolutionSettings;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Form;
use Filament\Pages\Page;
use Filament\Pages\SettingsPage;
use Illuminate\Contracts\Support\Htmlable;
@@ -29,6 +27,7 @@ class ManageSolutions extends SettingsPage
->schema([
TextInput::make('solutions_subtitle')
->label('Subtitle')
->required()
->maxLength(100),
TextInput::make('solutions_header')
->label('Header')
@@ -37,9 +36,11 @@ class ManageSolutions extends SettingsPage
Grid::make()->schema([
TextInput::make('solutions_button_text')
->label('Button Text')
->required()
->maxLength(50),
TextInput::make('solutions_button_url')
->label('Button URL')
->required()
->maxLength(255)
->url(),
])->columns(2),
@@ -103,4 +104,4 @@ class ManageSolutions extends SettingsPage
{
return 'Manage the solutions section content, including individual solution items.';
}
}
}

View File

@@ -4,10 +4,10 @@ namespace App\Filament\Pages;
use App\Settings\SuccessSettings;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Pages\SettingsPage;
use Illuminate\Contracts\Support\Htmlable;
@@ -28,6 +28,7 @@ class ManageSuccess extends SettingsPage
->schema([
TextInput::make('success_subtitle')
->label('Subtitle')
->required()
->maxLength(100),
TextInput::make('success_header')
->label('Header')
@@ -35,14 +36,17 @@ class ManageSuccess extends SettingsPage
->maxLength(255),
Textarea::make('success_paragraph')
->label('Paragraph')
->required()
->rows(3)
->maxLength(65535),
Grid::make()->schema([
TextInput::make('success_button_text')
->label('Button Text')
->required()
->maxLength(50),
TextInput::make('success_button_url')
->label('Button URL')
->required()
->maxLength(255)
->url(),
])->columns(2),
@@ -101,4 +105,4 @@ class ManageSuccess extends SettingsPage
{
return 'Manage the success section content, including text, button, and skill bars.';
}
}
}

View File

@@ -3,9 +3,7 @@
namespace App\Filament\Resources;
use App\Filament\Resources\BrandResource\Pages;
use App\Filament\Resources\BrandResource\RelationManagers;
use App\Models\Brand;
use Filament\Forms;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
@@ -16,8 +14,6 @@ use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
class BrandResource extends Resource
{
@@ -36,12 +32,12 @@ class BrandResource extends Resource
FileUpload::make('image')
->image()
->imageEditor()
->rules(['required', 'image',]),
->rules(['required', 'image']),
Toggle::make('active')
->label('Is active')
->onColor('success')
->offColor('danger')
->offColor('danger'),
]);
}

View File

@@ -0,0 +1,114 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\NewsResource\Pages;
use App\Models\News;
use Filament\Forms;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Table;
use Illuminate\Support\Str;
class NewsResource extends Resource
{
protected static ?string $model = News::class;
protected static ?string $navigationIcon = 'heroicon-o-newspaper';
protected static ?string $navigationGroup = 'CMS';
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Card::make()
->schema([
TextInput::make('title')
->required()
->maxLength(255)
->reactive()
->afterStateUpdated(fn (string $operation, $state, Forms\Set $set) => $operation === 'create' ? $set('slug', Str::slug($state)) : null),
TextInput::make('slug')
->required()
->maxLength(255)
->disabled()
->dehydrated()
->unique(News::class, 'slug', ignoreRecord: true),
FileUpload::make('image')
->image()
->directory('news')
->nullable()
->columnSpanFull(),
RichEditor::make('content')
->required()
->columnSpanFull(),
DateTimePicker::make('published_at')
->required()
->default(now()),
])
->columns(2),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
ImageColumn::make('image')
->square()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('title')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('slug')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('published_at')
->dateTime()
->sortable(),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListNews::route('/'),
'create' => Pages\CreateNews::route('/create'),
'edit' => Pages\EditNews::route('/{record}/edit'),
];
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\NewsResource\Pages;
use App\Filament\Resources\NewsResource;
use Filament\Resources\Pages\CreateRecord;
class CreateNews extends CreateRecord
{
protected static string $resource = NewsResource::class;
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\NewsResource\Pages;
use App\Filament\Resources\NewsResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditNews extends EditRecord
{
protected static string $resource = NewsResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\NewsResource\Pages;
use App\Filament\Resources\NewsResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListNews extends ListRecords
{
protected static string $resource = NewsResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -2,8 +2,6 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class BrandController extends Controller
{
//

View File

@@ -2,8 +2,8 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Internship;
use Illuminate\Http\Request;
class InternshipsPageController extends Controller
{

View File

@@ -8,7 +8,9 @@ class NewsPageController extends Controller
{
public function index()
{
return view('web.pages.news.index');
$allNews = News::all();
return view('web.pages.news.index', compact('allNews'));
}
public function show(News $news)

View File

@@ -10,8 +10,8 @@ class OurSolutionPageController extends Controller
{
return view('web.pages.our-solutions.index');
}
public function show(Solution $solution)
public function show(Solution $solution)
{
return view('web.pages.our-solutions.show', compact('solution'));
}

View File

@@ -9,7 +9,7 @@ use Illuminate\Support\Facades\Storage;
* @property int $id
* @property string $name
* @property string $image
* @property boolean $active
* @property bool $active
* @property \Illuminate\Support\Facades\Date $created_at
* @property \Illuminate\Support\Facades\Date $updated_at
*/
@@ -17,6 +17,7 @@ class Brand extends Model
{
/**
* Casts
*
* @var array<string, string>
*/
protected $casts = [
@@ -40,6 +41,6 @@ class Brand extends Model
*/
public function imageUrl(): string
{
return url('/storage/' . $this->image);
return url('/storage/'.$this->image);
}
}

View File

@@ -13,6 +13,7 @@ class News extends Model
'title',
'slug',
'content',
'image',
'published_at',
];
}
}

View File

@@ -14,4 +14,4 @@ class Solution extends Model
'description',
'slug',
];
}
}

View File

@@ -15,4 +15,4 @@ class Story extends Model
'content',
'published_at',
];
}
}

View File

@@ -2,11 +2,9 @@
namespace App\Providers;
use App\Models\Brand;
use App\Settings\SiteSettings;
use App\Settings\SiteSocialSettings;
use Illuminate\Database\Eloquent\Model;
use App\Settings\HomeSettings;
use Illuminate\Support\Facades\View as ViewFacade;
use Illuminate\Support\ServiceProvider;
use Illuminate\View\View;

View File

@@ -18,4 +18,4 @@ class PortfolioSettings extends Settings
{
return 'cms_portfolio';
}
}
}

View File

@@ -20,4 +20,4 @@ class SolutionSettings extends Settings
{
return 'cms_solutions';
}
}
}

View File

@@ -22,4 +22,4 @@ class SuccessSettings extends Settings
{
return 'cms_success';
}
}
}

View File

@@ -13,6 +13,11 @@ return new class extends Migration
{
Schema::create('news', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('slug')->unique();
$table->longText('content');
$table->string('image')->nullable();
$table->timestamp('published_at');
$table->timestamps();
});
}

View File

@@ -3,7 +3,6 @@
namespace Database\Seeders;
use App\Models\Brand;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\File;

View File

@@ -2,7 +2,6 @@
namespace Database\Seeders;
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

View File

@@ -2,8 +2,8 @@
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\User;
use Illuminate\Database\Seeder;
class UsersTableSeeder extends Seeder
{

View File

@@ -245,7 +245,7 @@
@foreach($homeSettings->industry_items as $item)
<div class="col-md-6 md-mb-25 wow fadeInUp" data-wow-delay=".4s">
<div class="industry__four-left-item borders pr-10 lg-pr-0">
<img src="/web/assets/img/icon/{{ $item['icon'] }}" alt="image">
<img src="/storage/{{ $item['icon'] }}" alt="image">
<h5>{{ $item['title'] }}</h5>
<p>{{ $item['description'] }}</p>
</div>
@@ -293,14 +293,14 @@
<div class="sliders text_scroll">
<ul>
@foreach($homeSettings->text_slide_items as $item)
<li><img src="/web/assets/img/icon/{{ $item['icon'] }}" alt="icon"><a href="{{ $item['link'] }}">{{ $item['text'] }}</a></li>
<li><img src="/storage/{{ $item['icon'] }}" alt="icon"><a href="{{ $item['link'] }}">{{ $item['text'] }}</a></li>
@endforeach
</ul>
</div>
<div class="sliders text_scroll">
<ul>
@foreach($homeSettings->text_slide_items as $item)
<li><img src="/web/assets/img/icon/{{ $item['icon'] }}" alt="icon"><a href="{{ $item['link'] }}">{{ $item['text'] }}</a></li>
<li><img src="/storage/{{ $item['icon'] }}" alt="icon"><a href="{{ $item['link'] }}">{{ $item['text'] }}</a></li>
@endforeach
</ul>
</div>

View File

@@ -1 +1,128 @@
w
@extends('web.layouts.app')
@section('content')
<!-- Breadcrumb Area Start -->
<div class="breadcrumb__area" style="background-image: url('assets/img/page/breadcrumb.jpg');">
<div class="container">
<div class="row">
<div class="col-xl-12">
<div class="breadcrumb__area-content">
<h2>Blog 3 Columns</h2>
<ul>
<li><a href="index.html">Home</a><i class="fa-regular fa-angle-right"></i></li>
<li>Blog 3 Columns</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Breadcrumb Area End -->
<!-- Blog Area Start -->
<div class="blog-three__columns section-padding-three">
<div class="container">
<div class="row">
@foreach ($allNews as $news)
<div class="col-xl-4 col-lg-6 wow fadeInUp" data-wow-delay=".4s">
<div class="blog__one-item">
<div class="blog__one-item-image">
<a href="{{ route('news.show', $news->slug) }}"><img src="assets/img/blog/blog-1.jpg" alt="image"></a>
<div class="blog__one-item-image-date">
<h6><i class="fa-regular fa-calendar"></i>{{ \Carbon\Carbon::parse($news->published_at)->format('d M') }}</h6>
</div>
</div>
<div class="blog__one-item-content">
<h4><a href="{{ route('news.show', $news->slug) }}">{{ $news->title }}</a></h4>
<a class="more_btn" href="{{ route('news.show', $news->slug) }}">Read More<i class="flaticon-right-up"></i></a>
</div>
</div>
</div>
@endforeach
<div class="col-xl-4 col-lg-6 wow fadeInUp" data-wow-delay=".7s">
<div class="blog__one-item">
<div class="blog__one-item-image">
<a href="blog-details.html"><img src="assets/img/blog/blog-2.jpg" alt="image"></a>
<div class="blog__one-item-image-date">
<h6><i class="fa-regular fa-calendar"></i>19 Dec</h6>
</div>
</div>
<div class="blog__one-item-content">
<h4><a href="blog-details.html">How to Choose the Perfect Construction Company</a></h4>
<a class="more_btn" href="blog-details.html">Read More<i class="flaticon-right-up"></i></a>
</div>
</div>
</div>
<div class="col-xl-4 col-lg-6 wow fadeInUp" data-wow-delay="1s">
<div class="blog__one-item">
<div class="blog__one-item-image">
<a href="blog-details.html"><img src="assets/img/blog/blog-3.jpg" alt="image"></a>
<div class="blog__one-item-image-date">
<h6><i class="fa-regular fa-calendar"></i>14 Dec</h6>
</div>
</div>
<div class="blog__one-item-content">
<h4><a href="blog-details.html">Top Mistakes to Avoid During Home Renovation</a></h4>
<a class="more_btn" href="blog-details.html">Read More<i class="flaticon-right-up"></i></a>
</div>
</div>
</div>
<div class="col-xl-4 col-lg-6 wow fadeInUp" data-wow-delay=".4s">
<div class="blog__one-item">
<div class="blog__one-item-image">
<a href="blog-details.html"><img src="assets/img/blog/blog-6.jpg" alt="image"></a>
<div class="blog__one-item-image-date">
<h6><i class="fa-regular fa-calendar"></i>22 Dec</h6>
</div>
</div>
<div class="blog__one-item-content">
<h4><a href="blog-details.html">Key Steps to Ensure a Smooth Building Process</a></h4>
<a class="more_btn" href="blog-details.html">Read More<i class="flaticon-right-up"></i></a>
</div>
</div>
</div>
<div class="col-xl-4 col-lg-6 wow fadeInUp" data-wow-delay=".7s">
<div class="blog__one-item">
<div class="blog__one-item-image">
<a href="blog-details.html"><img src="assets/img/blog/blog-5.jpg" alt="image"></a>
<div class="blog__one-item-image-date">
<h6><i class="fa-regular fa-calendar"></i>24 Dec</h6>
</div>
</div>
<div class="blog__one-item-content">
<h4><a href="blog-details.html">How to Maximize Space in Your Commercial Building</a></h4>
<a class="more_btn" href="blog-details.html">Read More<i class="flaticon-right-up"></i></a>
</div>
</div>
</div>
<div class="col-xl-4 col-lg-6 wow fadeInUp" data-wow-delay="1s">
<div class="blog__one-item">
<div class="blog__one-item-image">
<a href="blog-details.html"><img src="assets/img/blog/blog-4.jpg" alt="image"></a>
<div class="blog__one-item-image-date">
<h6><i class="fa-regular fa-calendar"></i>25 Dec</h6>
</div>
</div>
<div class="blog__one-item-content">
<h4><a href="blog-details.html">The Future of Smart Homes in Construction</a></h4>
<a class="more_btn" href="blog-details.html">Read More<i class="flaticon-right-up"></i></a>
</div>
</div>
</div>
</div>
<div class="row mt-25">
<div class="col-xl-12">
<div class="theme__pagination t-center">
<ul>
<li><a class="active" href="#">01</a></li>
<li><a href="#">02</a></li>
<li><a href="#"><i class="far fa-ellipsis-h"></i></a></li>
<li><a href="#">05</a></li>
<li><a href="#"><i class="fa-regular fa-angle-right"></i></a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Blog Area End -->
@endsection