changed username to slug

This commit is contained in:
Doar Mihai
2026-05-30 23:47:32 +03:00
parent ece3070676
commit bdb4457e77
15 changed files with 77 additions and 46 deletions
@@ -9,6 +9,7 @@ use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
@@ -36,9 +37,17 @@ class RegisteredUserController extends Controller
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$base = Str::slug($request->name);
$slug = $base;
$i = 1;
while (User::where('slug', $slug)->exists()) {
$slug = $base . '-' . $i++;
}
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'name' => $request->name,
'slug' => $slug,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
+1 -1
View File
@@ -9,7 +9,7 @@ class CompareController extends Controller
public function show(User $user)
{
if (auth()->id() === $user->id) {
return redirect()->route('users.show', $user->username);
return redirect()->route('users.show', $user->slug);
}
$myGames = auth()->user()->library()->with('game')->get()->keyBy('game_id');
+1 -1
View File
@@ -24,7 +24,7 @@ class FollowController extends Controller
'type' => 'new_follower',
'data' => json_encode([
'follower_id' => $me->id,
'follower_username' => $me->username,
'follower_slug' => $me->slug,
'follower_name' => $me->name,
]),
'created_at' => now(),
@@ -32,7 +32,7 @@ class GameCommentController extends Controller
return response()->json([
'id' => $comment->id,
'body' => $comment->body,
'user' => ['name' => $comment->user->name, 'username' => $comment->user->username],
'user' => ['name' => $comment->user->name, 'slug' => $comment->user->slug],
'user_id' => $comment->user_id,
'created_at' => $comment->created_at->diffForHumans(),
'replies_count' => 0,
+2 -2
View File
@@ -10,11 +10,11 @@ class User extends Authenticatable
{
use HasFactory, Notifiable;
protected $fillable = ['name','username','email','password','avatar','bio','location','website','is_private','is_admin'];
protected $fillable = ['name','slug','email','password','avatar','bio','location','website','is_private','is_admin'];
protected $hidden = ['password','remember_token'];
protected $casts = ['email_verified_at'=>'datetime','password'=>'hashed','is_private'=>'boolean','is_admin'=>'boolean'];
public function getRouteKeyName(): string { return 'username'; }
public function getRouteKeyName(): string { return 'slug'; }
public function library(): HasMany { return $this->hasMany(UserGame::class); }
public function reviews(): HasMany { return $this->hasMany(Review::class); }
@@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('username', 'slug');
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('slug', 'username');
});
}
};
+3 -3
View File
@@ -40,7 +40,7 @@
@endphp
<div class="bg-[#16161d] border border-white/5 rounded-xl p-4 flex items-start gap-4">
{{-- User avatar --}}
<a href="{{ $activity->user ? route('users.show', $activity->user->username) : '#' }}" class="flex-shrink-0">
<a href="{{ $activity->user ? route('users.show', $activity->user->slug) : '#' }}" class="flex-shrink-0">
@if($activity->user?->avatar)
<img src="{{ $activity->user->avatar }}" class="w-9 h-9 rounded-full object-cover border border-white/10" alt="">
@else
@@ -61,9 +61,9 @@
{{-- Activity text --}}
<div class="flex-1 min-w-0">
<p class="text-sm text-slate-300">
<a href="{{ $activity->user ? route('users.show', $activity->user->username) : '#' }}"
<a href="{{ $activity->user ? route('users.show', $activity->user->slug) : '#' }}"
class="font-semibold text-white hover:text-purple-300 transition-colors">
{{ $activity->user?->username ?? 'Unknown' }}
{{ $activity->user?->slug ?? 'Unknown' }}
</a>
@if($activity->type === 'added_to_library')
+4 -4
View File
@@ -1,6 +1,6 @@
@extends('layouts.app')
@section('title', 'Your Library vs ' . ($user->username ?? $user->name) . ' — GameLog')
@section('title', 'Your Library vs ' . ($user->slug ?? $user->name) . ' — GameLog')
@section('content')
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10"
@@ -8,12 +8,12 @@
{{-- Header --}}
<div class="mb-8">
<a href="{{ route('users.show', $user->username) }}"
<a href="{{ route('users.show', $user->slug) }}"
class="text-sm text-slate-500 hover:text-slate-300 transition-colors mb-2 inline-block">
← {{ $user->username ?? $user->name }}'s profile
← {{ $user->slug ?? $user->name }}'s profile
</a>
<h1 class="text-2xl font-bold text-white">
Your Library vs <span class="text-purple-400">{{ $user->username ?? $user->name }}</span>'s Library
Your Library vs <span class="text-purple-400">{{ $user->slug ?? $user->name }}</span>'s Library
</h1>
</div>
+5 -5
View File
@@ -321,13 +321,13 @@
$commentsData = $comments->map(fn($c) => [
'id' => $c->id,
'body' => $c->body,
'user' => ['name' => $c->user->name, 'username' => $c->user->username],
'user' => ['name' => $c->user->name, 'slug' => $c->user->slug],
'user_id' => $c->user_id,
'created_at' => $c->created_at->diffForHumans(),
'replies' => $c->replies->map(fn($r) => [
'id' => $r->id,
'body' => $r->body,
'user' => ['name' => $r->user->name, 'username' => $r->user->username],
'user' => ['name' => $r->user->name, 'slug' => $r->user->slug],
'user_id' => $r->user_id,
'created_at' => $r->created_at->diffForHumans(),
])->values()->all(),
@@ -375,7 +375,7 @@
<div class="flex items-center gap-2">
<div class="w-7 h-7 rounded-full bg-purple-700 flex items-center justify-center text-xs font-bold text-white flex-shrink-0"
x-text="comment.user.name.charAt(0).toUpperCase()"></div>
<span class="text-sm font-medium text-slate-300" x-text="comment.user.username || comment.user.name"></span>
<span class="text-sm font-medium text-slate-300" x-text="comment.user.slug || comment.user.name"></span>
<span class="text-xs text-slate-600" x-text="comment.created_at"></span>
</div>
<div class="flex items-center gap-2">
@@ -397,7 +397,7 @@
<div class="flex items-center gap-2">
<div class="w-5 h-5 rounded-full bg-purple-800 flex items-center justify-center text-xs font-bold text-white flex-shrink-0"
x-text="reply.user.name.charAt(0).toUpperCase()"></div>
<span class="text-xs font-medium text-slate-300" x-text="reply.user.username || reply.user.name"></span>
<span class="text-xs font-medium text-slate-300" x-text="reply.user.slug || reply.user.name"></span>
<span class="text-xs text-slate-600" x-text="reply.created_at"></span>
</div>
@auth
@@ -451,7 +451,7 @@
<div class="w-7 h-7 rounded-full bg-purple-700 flex items-center justify-center text-xs font-bold text-white">
{{ strtoupper(substr($review->user->name, 0, 1)) }}
</div>
<span class="text-sm font-medium text-slate-300">{{ $review->user->username ?? $review->user->name }}</span>
<span class="text-sm font-medium text-slate-300">{{ $review->user->slug ?? $review->user->name }}</span>
@if($review->has_spoilers)
<span class="px-1.5 py-0.5 text-xs bg-yellow-900/40 text-yellow-400 border border-yellow-700/30 rounded">Spoilers</span>
@endif
+4 -4
View File
@@ -121,16 +121,16 @@
{{ strtoupper(substr(auth()->user()->name, 0, 1)) }}
</div>
@endif
<span class="hidden sm:block">{{ auth()->user()->username ?? auth()->user()->name }}</span>
<span class="hidden sm:block">{{ auth()->user()->slug ?? auth()->user()->name }}</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<div x-show="open" @click.outside="open = false" x-transition
class="absolute right-0 mt-2 w-48 bg-[#16161d] border border-white/10 rounded-lg shadow-xl overflow-hidden z-50">
@if(auth()->user()->username)
<a href="{{ route('users.show', auth()->user()->username) }}" class="block px-4 py-2.5 text-sm text-slate-300 hover:bg-white/5 hover:text-white transition-colors">Profile</a>
<a href="{{ route('users.library', auth()->user()->username) }}" class="block px-4 py-2.5 text-sm text-slate-300 hover:bg-white/5 hover:text-white transition-colors">My Library</a>
@if(auth()->user()->slug)
<a href="{{ route('users.show', auth()->user()->slug) }}" class="block px-4 py-2.5 text-sm text-slate-300 hover:bg-white/5 hover:text-white transition-colors">Profile</a>
<a href="{{ route('users.library', auth()->user()->slug) }}" class="block px-4 py-2.5 text-sm text-slate-300 hover:bg-white/5 hover:text-white transition-colors">My Library</a>
<a href="{{ route('lists.index') }}" class="block px-4 py-2.5 text-sm text-slate-300 hover:bg-white/5 hover:text-white transition-colors">My Lists</a>
@endif
<a href="{{ route('profile.edit') }}" class="block px-4 py-2.5 text-sm text-slate-300 hover:bg-white/5 hover:text-white transition-colors">Settings</a>
+2 -2
View File
@@ -10,8 +10,8 @@
<div class="flex items-start justify-between mb-2">
<div>
<div class="flex items-center gap-2 mb-1">
<a href="{{ route('users.show', ['user' => $list->user->username]) }}" class="text-sm text-slate-500 hover:text-slate-300 transition-colors">
{{ $list->user->username ?? $list->user->name }}
<a href="{{ route('users.show', ['user' => $list->user->slug]) }}" class="text-sm text-slate-500 hover:text-slate-300 transition-colors">
{{ $list->user->slug ?? $list->user->name }}
</a>
@if(!$list->is_public)
<span class="text-xs text-slate-500 bg-[#16161d] px-2 py-0.5 rounded border border-white/10">Private</span>
@@ -28,10 +28,10 @@
</div>
<div class="flex-1 min-w-0">
<p class="text-sm text-slate-300">
@if(!empty($data['follower_username']))
<a href="{{ route('users.show', $data['follower_username']) }}"
@if(!empty($data['follower_slug']))
<a href="{{ route('users.show', $data['follower_slug']) }}"
class="font-semibold text-white hover:text-purple-300 transition-colors">
{{ $data['follower_name'] ?? $data['follower_username'] }}
{{ $data['follower_name'] ?? $data['follower_slug'] }}
</a>
@else
<span class="font-semibold text-white">Someone</span>
@@ -40,8 +40,8 @@
</p>
<p class="text-xs text-slate-600 mt-0.5">{{ $notification->created_at?->diffForHumans() }}</p>
</div>
@if(!empty($data['follower_username']))
<a href="{{ route('users.show', $data['follower_username']) }}"
@if(!empty($data['follower_slug']))
<a href="{{ route('users.show', $data['follower_slug']) }}"
class="text-xs text-purple-400 hover:text-purple-300 transition-colors flex-shrink-0">
View profile →
</a>
+4 -4
View File
@@ -1,6 +1,6 @@
@extends('layouts.app')
@section('title', ($user->username ?? $user->name) . "'s Library — GameLog")
@section('title', ($user->slug ?? $user->name) . "'s Library — GameLog")
@section('content')
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
@@ -8,8 +8,8 @@
<div class="flex items-center justify-between mb-8">
<div>
<h1 class="text-2xl font-bold text-white">
<a href="{{ route('users.show', $user->username) }}" class="hover:text-purple-400 transition-colors">
{{ $user->username ?? $user->name }}
<a href="{{ route('users.show', $user->slug) }}" class="hover:text-purple-400 transition-colors">
{{ $user->slug ?? $user->name }}
</a>'s Library
</h1>
</div>
@@ -21,7 +21,7 @@
$tabs = ['' => 'All', 'playing' => 'Playing', 'played' => 'Played', 'want_to_play' => 'Want to Play', 'on_hold' => 'On Hold', 'dropped' => 'Dropped'];
@endphp
@foreach($tabs as $tabStatus => $tabLabel)
<a href="{{ route('users.library', ['user' => $user->username, 'status' => $tabStatus ?: null]) }}"
<a href="{{ route('users.library', ['user' => $user->slug, 'status' => $tabStatus ?: null]) }}"
class="px-4 py-2 rounded-lg text-sm font-medium transition-colors
{{ $status === $tabStatus || (!$status && !$tabStatus)
? 'bg-purple-600 text-white'
+7 -7
View File
@@ -1,6 +1,6 @@
@extends('layouts.app')
@section('title', ($user->username ?? $user->name) . ' — GameLog')
@section('title', ($user->slug ?? $user->name) . ' — GameLog')
@section('content')
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
@@ -16,8 +16,8 @@
@endif
<div class="flex-1">
<h1 class="text-2xl font-bold text-white">{{ $user->username ?? $user->name }}</h1>
@if($user->username && $user->name !== $user->username)
<h1 class="text-2xl font-bold text-white">{{ $user->slug ?? $user->name }}</h1>
@if($user->slug && $user->name !== $user->slug)
<p class="text-slate-500 text-sm">{{ $user->name }}</p>
@endif
@if($user->bio)
@@ -65,7 +65,7 @@
following: {{ auth()->user()->isFollowing($user) ? 'true' : 'false' }},
async toggle() {
const method = this.following ? 'DELETE' : 'POST';
const res = await fetch('{{ route('users.follow', $user->username) }}', {
const res = await fetch('{{ route('users.follow', $user->slug) }}', {
method,
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content,
@@ -88,13 +88,13 @@
</div>
@endif
@endauth
<a href="{{ route('users.library', $user->username) }}"
<a href="{{ route('users.library', $user->slug) }}"
class="px-4 py-2 border border-white/10 hover:border-white/20 text-slate-300 hover:text-white text-sm font-medium rounded-lg transition-colors">
View Library
</a>
@auth
@if(auth()->id() !== $user->id)
<a href="{{ route('users.compare', $user->username) }}"
<a href="{{ route('users.compare', $user->slug) }}"
class="px-4 py-2 border border-purple-500/40 hover:border-purple-500 text-purple-300 hover:text-white text-sm font-medium rounded-lg transition-colors">
Compare Libraries
</a>
@@ -124,7 +124,7 @@
<div>
<div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-semibold text-white">Recent Library</h2>
<a href="{{ route('users.library', $user->username) }}" class="text-sm text-purple-400 hover:text-purple-300 transition-colors">
<a href="{{ route('users.library', $user->slug) }}" class="text-sm text-purple-400 hover:text-purple-300 transition-colors">
View all →
</a>
</div>
+5 -5
View File
@@ -20,12 +20,12 @@ Route::get('/new-releases', [GameController::class, 'newReleases'])->name('games
Route::get('/games/{game:slug}', [GameController::class, 'show'])->name('games.show');
// User profiles (public)
Route::get('/users/{user:username}', [UserController::class, 'show'])->name('users.show');
Route::get('/users/{user:username}/library', [UserController::class, 'library'])->name('users.library');
Route::get('/users/{user:slug}', [UserController::class, 'show'])->name('users.show');
Route::get('/users/{user:slug}/library', [UserController::class, 'library'])->name('users.library');
// Compare (auth required)
Route::middleware('auth')->group(function () {
Route::get('/users/{user:username}/compare', [CompareController::class, 'show'])->name('users.compare');
Route::get('/users/{user:slug}/compare', [CompareController::class, 'show'])->name('users.compare');
});
// Library (auth required)
@@ -58,8 +58,8 @@ Route::delete('/lists/{list:slug}/games/{game}', [\App\Http\Controllers\ListCont
// Follow system
Route::middleware('auth')->group(function () {
Route::post('/users/{user:username}/follow', [\App\Http\Controllers\FollowController::class, 'store'])->name('users.follow');
Route::delete('/users/{user:username}/follow', [\App\Http\Controllers\FollowController::class, 'destroy'])->name('users.unfollow');
Route::post('/users/{user:slug}/follow', [\App\Http\Controllers\FollowController::class, 'store'])->name('users.follow');
Route::delete('/users/{user:slug}/follow', [\App\Http\Controllers\FollowController::class, 'destroy'])->name('users.unfollow');
});
// Activity feed