Skip to content

Commit 4e5254d

Browse files
committed
slug based routing on products
1 parent 464b81a commit 4e5254d

File tree

7 files changed

+92
-57
lines changed

7 files changed

+92
-57
lines changed

app/Filament/Admin/Resources/Products/ProductResource.php

Lines changed: 69 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
use Filament\Forms\Components\DateTimePicker;
3434
use Filament\Forms\Components\Select;
3535
use Filament\Forms\Components\TextInput;
36+
use Filament\Schemas\Components\Utilities\Get;
37+
use Filament\Schemas\Components\Utilities\Set;
38+
use Illuminate\Support\Str;
3639

3740
class ProductResource extends Resource
3841
{
@@ -46,33 +49,42 @@ public static function form(Schema $schema): Schema
4649
{
4750
return $schema
4851
->components([
49-
TextInput::make('name')
50-
->required()
51-
->maxLength(255),
52-
Textarea::make('description')
53-
->maxLength(65535),
54-
TextInput::make('price')
55-
->required()
56-
->numeric()
57-
->prefix('$'),
58-
Select::make('category_id')
59-
->label('Category')
60-
->options(ProductCategory::all()->pluck('name', 'id'))
61-
->searchable(),
62-
TextInput::make('inventory_count')
63-
->required()
64-
->numeric()
65-
->minValue(0),
66-
TextInput::make('low_stock_threshold')
67-
->required()
68-
->numeric()
69-
->minValue(0)
70-
->label('Low Stock Threshold'),
71-
Select::make('tags')
72-
->multiple()
73-
->relationship('tags', 'name')
74-
->preload(),
52+
Section::make('Basic Information')
53+
->columnSpanFull()
54+
->columns(2)
55+
->schema([
56+
TextInput::make('name')
57+
->required()
58+
->maxLength(255)
59+
->live(onBlur: true)
60+
->afterStateUpdated(function (Set $set, Get $get, ?string $old, ?string $state): void {
61+
$currentSlug = (string) ($get('slug') ?? '');
62+
$oldNameSlug = Str::slug((string) ($old ?? ''));
63+
if ($currentSlug !== '' && $currentSlug !== $oldNameSlug) {
64+
return;
65+
}
66+
$set('slug', Str::slug((string) $state));
67+
}),
68+
TextInput::make('slug')
69+
->required()
70+
->unique(ignoreRecord: true)
71+
->maxLength(255),
72+
Select::make('category_id')
73+
->label('Category')
74+
->options(ProductCategory::all()->pluck('name', 'id'))
75+
->searchable(),
76+
Select::make('tags')
77+
->multiple()
78+
->relationship('tags', 'name')
79+
->preload(),
80+
Textarea::make('description')
81+
->maxLength(65535)
82+
->columnSpanFull(),
83+
]),
84+
7585
Section::make('Pricing')
86+
->columnSpanFull()
87+
->columns(2)
7688
->schema([
7789
Select::make('pricing_type')
7890
->options([
@@ -81,51 +93,65 @@ public static function form(Schema $schema): Schema
8193
'donation' => 'Pay What You Want',
8294
])
8395
->default('fixed')
84-
->reactive(),
85-
96+
->live(),
8697
TextInput::make('price')
8798
->required()
8899
->numeric()
89100
->prefix('$')
90-
->visible(fn (callable $get) => $get('pricing_type') === 'fixed'),
91-
101+
->visible(fn (Get $get) => $get('pricing_type') === 'fixed'),
92102
TextInput::make('suggested_price')
93103
->numeric()
94104
->prefix('$')
95-
->visible(fn (callable $get) => $get('pricing_type') === 'donation'),
96-
105+
->visible(fn (Get $get) => $get('pricing_type') === 'donation'),
97106
TextInput::make('minimum_price')
98107
->numeric()
99108
->prefix('$')
100109
->default(0)
101-
->visible(fn (callable $get) => $get('pricing_type') === 'donation'),
110+
->visible(fn (Get $get) => $get('pricing_type') === 'donation'),
102111
]),
112+
113+
Section::make('Inventory')
114+
->columnSpanFull()
115+
->columns(2)
116+
->schema([
117+
TextInput::make('inventory_count')
118+
->required()
119+
->numeric()
120+
->minValue(0),
121+
TextInput::make('low_stock_threshold')
122+
->required()
123+
->numeric()
124+
->minValue(0)
125+
->label('Low Stock Threshold'),
126+
]),
127+
103128
Section::make('Downloadable Product')
129+
->columnSpanFull()
130+
->columns(2)
131+
->collapsible()
104132
->schema([
105133
Toggle::make('is_downloadable')
106134
->label('Is Downloadable Product')
107-
->reactive(),
108-
135+
->live()
136+
->columnSpanFull(),
109137
FileUpload::make('downloadable_file')
110138
->label('Product File')
111139
->disk('local')
112140
->directory('downloadable_products')
113141
->visibility('private')
114142
->acceptedFileTypes(['application/pdf', 'application/zip'])
115-
->maxSize(50 * 1024) // 50MB
116-
->visible(fn (callable $get) => $get('is_downloadable')),
117-
143+
->maxSize(50 * 1024)
144+
->visible(fn (Get $get) => $get('is_downloadable'))
145+
->columnSpanFull(),
118146
TextInput::make('download_limit')
119147
->label('Download Limit')
120148
->numeric()
121149
->minValue(1)
122-
->visible(fn (callable $get) => $get('is_downloadable')),
123-
150+
->visible(fn (Get $get) => $get('is_downloadable')),
124151
DateTimePicker::make('expiration_time')
125152
->label('Download Expiration')
126-
->visible(fn (callable $get) => $get('is_downloadable')),
127-
])
128-
->collapsible(),
153+
->visible(fn (Get $get) => $get('is_downloadable')),
154+
]),
129155
]);
130156
}
131157

app/Models/Product.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ class Product extends Model implements Orderable
5050
'minimum_price' => 'decimal:2',
5151
];
5252

53+
public function getRouteKeyName(): string
54+
{
55+
return 'slug';
56+
}
57+
5358
public function category()
5459
{
5560
return $this->belongsTo(ProductCategory::class);

database/seeders/DummyData/ProductSeeder.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ class ProductSeeder extends Seeder
1414
*/
1515
public function run(): void
1616
{
17-
// Fetch categories by name to avoid hardcoding IDs
18-
$electronicsCategory = ProductCategory::where('name', 'Electronics')->first();
19-
$clothingCategory = ProductCategory::where('name', 'Clothing')->first();
20-
$booksCategory = ProductCategory::where('name', 'Books')->first();
21-
$homeKitchenCategory = ProductCategory::where('name', 'Home & Kitchen')->first();
22-
$sportsOutdoorsCategory = ProductCategory::where('name', 'Sports & Outdoors')->first();
23-
$healthBeautyCategory = ProductCategory::where('name', 'Health & Beauty')->first();
24-
$toysGamesCategory = ProductCategory::where('name', 'Toys & Games')->first();
17+
// Fetch categories by slug to avoid hardcoding IDs
18+
$electronicsCategory = ProductCategory::where('slug', 'electronics')->first();
19+
$clothingCategory = ProductCategory::where('slug', 'clothing')->first();
20+
$booksCategory = ProductCategory::where('slug', 'books')->first();
21+
$homeKitchenCategory = ProductCategory::where('slug', 'home')->first();
22+
$sportsOutdoorsCategory = ProductCategory::where('slug', 'sports')->first();
23+
$healthBeautyCategory = ProductCategory::where('slug', 'beauty')->first();
24+
$toysGamesCategory = ProductCategory::where('slug', 'toys')->first();
2525

2626
// Products for Electronics
2727
Product::factory()->create([

resources/views/home.blade.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ class="btn btn-secondary group mt-4 sm:mt-0">
163163
@foreach($latestProducts as $product)
164164
<div class="product-card group">
165165
<div class="relative overflow-hidden">
166-
<a href="{{ route('products.show', $product->id) }}">
166+
<a href="{{ route('products.show', $product) }}">
167167
<img src="{{ $product->image_url ?? asset('images/placeholder.png') }}"
168168
alt="{{ $product->name }}"
169169
class="product-image">
@@ -200,7 +200,7 @@ class="product-image">
200200

201201
<div class="p-6">
202202
<div class="mb-2">
203-
<a href="{{ route('products.show', $product->id) }}"
203+
<a href="{{ route('products.show', $product) }}"
204204
class="text-lg font-semibold text-gray-900 hover:text-blue-600 transition-colors line-clamp-2">
205205
{{ $product->name }}
206206
</a>

resources/views/product.blade.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898

9999
<div class="flex space-x-4">
100100
@auth
101+
@if(Route::has('wishlist.add') && Route::has('wishlist.remove'))
101102
@if(auth()->user()->wishlist()->where('product_id', $product->id)->exists())
102103
<form action="{{ route('wishlist.remove', $product) }}" method="POST">
103104
@csrf
@@ -120,6 +121,7 @@
120121
</button>
121122
</form>
122123
@endif
124+
@endif
123125
@endauth
124126
<button class="flex items-center text-gray-600 hover:text-blue-600">
125127
<svg class="h-5 w-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">

resources/views/products/compare.blade.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
<td>Actions</td>
5050
@foreach($products as $product)
5151
<td>
52-
<a href="{{ route('products.show', $product->id) }}" class="btn btn-sm btn-primary">View</a>
52+
<a href="{{ route('products.show', $product) }}" class="btn btn-sm btn-primary">View</a>
5353
<form action="{{ route('products.removeFromCompare', $product->id) }}" method="POST" class="d-inline">
5454
@csrf
5555
@method('DELETE')

resources/views/products/show.blade.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class="w-20 rounded border-gray-300"
6464
<p class="text-danger"><strong>Out of Stock</strong></p>
6565
@endif
6666
@auth
67+
@if(Route::has('wishlist.add') && Route::has('wishlist.remove'))
6768
@if(auth()->user()->wishlist()->where('product_id', $product->id)->exists())
6869
<form action="{{ route('wishlist.remove', $product) }}" method="POST">
6970
@csrf
@@ -76,6 +77,7 @@ class="w-20 rounded border-gray-300"
7677
<button type="submit" class="btn btn-primary">Add to Wishlist</button>
7778
</form>
7879
@endif
80+
@endif
7981
@endauth
8082
{{-- <form action="{{ route('products.addToCompare', $product) }}" method="POST" class="d-inline">
8183
@csrf
@@ -97,7 +99,7 @@ class="w-20 rounded border-gray-300"
9799
<div class="card-body">
98100
<h5 class="card-title">{{ $recommendedProduct->name }}</h5>
99101
<p class="card-text">${{ number_format($recommendedProduct->price, 2) }}</p>
100-
<a href="{{ route('products.show', $recommendedProduct->id) }}" class="btn btn-sm btn-primary">View Product</a>
102+
<a href="{{ route('products.show', $recommendedProduct) }}" class="btn btn-sm btn-primary">View Product</a>
101103
@if($recommendedProduct->inventory_count == 0)
102104
<p class="text-danger mt-2">Out of Stock</p>
103105
@endif
@@ -159,7 +161,7 @@ class="w-20 rounded border-gray-300"
159161
],
160162
'offers' => [
161163
'@type' => 'Offer',
162-
'url' => route('products.show', $product->id),
164+
'url' => route('products.show', $product),
163165
'priceCurrency' => 'USD',
164166
'price' => $product->price,
165167
'availability' => $product->inventory_count > 0
@@ -181,6 +183,6 @@ class="w-20 rounded border-gray-300"
181183
<meta property="og:title" content="{{ $product->meta_title ?? $product->name }}">
182184
<meta property="og:description" content="{{ $product->meta_description ?? $product->short_description }}">
183185
<meta property="og:image" content="{{ asset('/images/placeholder.png') }}">
184-
<meta property="og:url" content="{{ route('products.show', $product->id) }}">
186+
<meta property="og:url" content="{{ route('products.show', $product) }}">
185187
<meta name="twitter:card" content="summary_large_image">
186188
@endsection

0 commit comments

Comments
 (0)