Add reviews, search autocomplete, SEO meta/schema, user lists

- Review system: ReviewController (store/update/destroy), ReviewPolicy, Review model boot() hooks, review form with Alpine.js (spoiler toggle, edit/delete UX)
- Search autocomplete: Alpine.js searchWidget in nav with 300ms debounce, keyboard navigation, 6-result dropdown
- SEO: og:image, twitter:card in layout; og sections + Schema.org VideoGame JSON-LD on game show pages
- Custom lists: UserList/UserListGame models, ListController, ListPolicy, lists index/show views, Add-to-List button on game pages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mark
2026-05-30 02:50:34 +03:00
co-authored by Claude Sonnet 4.6
parent 93d869cf8c
commit 5c6ba86e15
14 changed files with 795 additions and 39 deletions
+18 -2
View File
@@ -1,13 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Review extends Model
{
protected $fillable = ['user_id','game_id','rating','body','has_spoilers','helpful_count'];
protected $casts = ['has_spoilers'=>'boolean'];
protected $fillable = ['user_id', 'game_id', 'body', 'rating', 'has_spoilers'];
protected $casts = ['has_spoilers' => 'boolean'];
protected static function boot(): void
{
parent::boot();
static::saved(function (Review $review) {
$review->game->recalculateRating();
});
static::deleted(function (Review $review) {
$review->game->recalculateRating();
});
}
public function user(): BelongsTo { return $this->belongsTo(User::class); }
public function game(): BelongsTo { return $this->belongsTo(Game::class); }
+24 -1
View File
@@ -3,8 +3,31 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class UserList extends Model
{
//
protected $fillable = ['user_id', 'title', 'slug', 'description', 'is_public'];
protected $casts = ['is_public' => 'boolean'];
protected static function boot(): void
{
parent::boot();
static::creating(function (UserList $list) {
if (empty($list->slug)) {
$list->slug = Str::slug($list->title);
}
});
}
public function user(): BelongsTo { return $this->belongsTo(User::class); }
public function listGames(): HasMany
{
return $this->hasMany(UserListGame::class, 'user_list_id')->orderBy('order');
}
}
+5 -1
View File
@@ -3,8 +3,12 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class UserListGame extends Model
{
//
protected $fillable = ['user_list_id', 'game_id', 'order', 'notes'];
public function list(): BelongsTo { return $this->belongsTo(UserList::class, 'user_list_id'); }
public function game(): BelongsTo { return $this->belongsTo(Game::class); }
}