mirror of https://github.com/kcal-app/kcal.git
				
				
				
			Create "profile" controller and routes (WIP)
Views are incomplete and components need to be broken out from user edit.
This commit is contained in:
		
							parent
							
								
									104bbcd614
								
							
						
					
					
						commit
						a378936b72
					
				|  | @ -0,0 +1,42 @@ | ||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Http\Controllers; | ||||||
|  | 
 | ||||||
|  | use App\Http\Controllers\Traits\UpdatesUser; | ||||||
|  | use App\Http\Requests\UpdateUserRequest; | ||||||
|  | use App\Models\User; | ||||||
|  | use Illuminate\Contracts\View\View; | ||||||
|  | use Illuminate\Http\RedirectResponse; | ||||||
|  | 
 | ||||||
|  | class ProfileController extends Controller | ||||||
|  | { | ||||||
|  |     use UpdatesUser; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Display a user profile page. | ||||||
|  |      */ | ||||||
|  |     public function show(User $user): View | ||||||
|  |     { | ||||||
|  |         return view('profiles.show')->with('user', $user); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Show the form for editing a user's profile data. | ||||||
|  |      */ | ||||||
|  |     public function edit(User $user): View | ||||||
|  |     { | ||||||
|  |         return view('profiles.edit')->with('user', $user); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Update the user profile data. | ||||||
|  |      */ | ||||||
|  |     public function update(UpdateUserRequest $request, User $user): RedirectResponse | ||||||
|  |     { | ||||||
|  |         $this->updateUser($request, $user); | ||||||
|  |         $user->refresh(); | ||||||
|  |         session()->flash('message', "Profile updated!"); | ||||||
|  |         return redirect()->route('profiles.show', $user); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,45 @@ | ||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Http\Controllers\Traits; | ||||||
|  | 
 | ||||||
|  | use App\Http\Requests\UpdateUserRequest; | ||||||
|  | use App\Models\User; | ||||||
|  | use Illuminate\Support\Facades\Hash; | ||||||
|  | use Illuminate\Support\Str; | ||||||
|  | 
 | ||||||
|  | trait UpdatesUser | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Updates a user from a user update request. | ||||||
|  |      */ | ||||||
|  |     public function updateUser(UpdateUserRequest $request, User $user): void { | ||||||
|  |         $input = $request->validated(); | ||||||
|  |         $input['remember_token'] = Str::random(10); | ||||||
|  |         if (!empty($input['password'])) { | ||||||
|  |             $input['password'] = Hash::make($input['password']); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             unset($input['password']); | ||||||
|  |         } | ||||||
|  |         $input['admin'] = $input['admin'] ?? false; | ||||||
|  | 
 | ||||||
|  |         $user->fill($input)->save(); | ||||||
|  | 
 | ||||||
|  |         // Handle image.
 | ||||||
|  |         if (!empty($input['image'])) { | ||||||
|  |             /** @var \Illuminate\Http\UploadedFile $file */ | ||||||
|  |             $file = $input['image']; | ||||||
|  |             $user->clearMediaCollection(); | ||||||
|  |             $user | ||||||
|  |                 ->addMediaFromRequest('image') | ||||||
|  |                 ->usingName($user->username) | ||||||
|  |                 ->usingFileName("{$user->slug}.{$file->extension()}") | ||||||
|  |                 ->toMediaCollection(); | ||||||
|  |         } | ||||||
|  |         elseif (isset($input['remove_image']) && $input['remove_image']) { | ||||||
|  |             $user->clearMediaCollection(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -2,15 +2,16 @@ | ||||||
| 
 | 
 | ||||||
| namespace App\Http\Controllers; | namespace App\Http\Controllers; | ||||||
| 
 | 
 | ||||||
|  | use App\Http\Controllers\Traits\UpdatesUser; | ||||||
| use App\Http\Requests\UpdateUserRequest; | use App\Http\Requests\UpdateUserRequest; | ||||||
| use App\Models\User; | use App\Models\User; | ||||||
| use Illuminate\Contracts\View\View; | use Illuminate\Contracts\View\View; | ||||||
| use Illuminate\Http\RedirectResponse; | use Illuminate\Http\RedirectResponse; | ||||||
| use Illuminate\Support\Facades\Hash; |  | ||||||
| use Illuminate\Support\Str; |  | ||||||
| 
 | 
 | ||||||
| class UserController extends Controller | class UserController extends Controller | ||||||
| { | { | ||||||
|  |     use UpdatesUser; | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Display a listing of the resource. |      * Display a listing of the resource. | ||||||
|      */ |      */ | ||||||
|  | @ -50,33 +51,7 @@ class UserController extends Controller | ||||||
|      */ |      */ | ||||||
|     public function update(UpdateUserRequest $request, User $user): RedirectResponse |     public function update(UpdateUserRequest $request, User $user): RedirectResponse | ||||||
|     { |     { | ||||||
|         $input = $request->validated(); |         $this->updateUser($request, $user); | ||||||
|         $input['remember_token'] = Str::random(10); |  | ||||||
|         if (!empty($input['password'])) { |  | ||||||
|             $input['password'] = Hash::make($input['password']); |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             unset($input['password']); |  | ||||||
|         } |  | ||||||
|         $input['admin'] = $input['admin'] ?? false; |  | ||||||
| 
 |  | ||||||
|         $user->fill($input)->save(); |  | ||||||
| 
 |  | ||||||
|         // Handle image.
 |  | ||||||
|         if (!empty($input['image'])) { |  | ||||||
|             /** @var \Illuminate\Http\UploadedFile $file */ |  | ||||||
|             $file = $input['image']; |  | ||||||
|             $user->clearMediaCollection(); |  | ||||||
|             $user |  | ||||||
|                 ->addMediaFromRequest('image') |  | ||||||
|                 ->usingName($user->username) |  | ||||||
|                 ->usingFileName("{$user->slug}.{$file->extension()}") |  | ||||||
|                 ->toMediaCollection(); |  | ||||||
|         } |  | ||||||
|         elseif (isset($input['remove_image']) && $input['remove_image']) { |  | ||||||
|             $user->clearMediaCollection(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         session()->flash('message', "User {$user->name} updated!"); |         session()->flash('message', "User {$user->name} updated!"); | ||||||
|         return redirect()->route('users.index'); |         return redirect()->route('users.index'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -83,6 +83,14 @@ final class User extends Authenticatable implements HasMedia | ||||||
|         'admin' => 'bool', |         'admin' => 'bool', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * @inheritdoc | ||||||
|  |      */ | ||||||
|  |     public function sluggable(): array | ||||||
|  |     { | ||||||
|  |         return ['slug' => ['source' => 'username']]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Get the User's goals. |      * Get the User's goals. | ||||||
|      */ |      */ | ||||||
|  |  | ||||||
|  | @ -16,6 +16,13 @@ class UserPolicy | ||||||
|         return $user->admin; |         return $user->admin; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Determine whether the user can edit a user via the "profile". | ||||||
|  |      */ | ||||||
|  |     public function editProfile(User $user, User $model): bool { | ||||||
|  |         return $user->id === $model->id; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Determine whether the user can delete the model. |      * Determine whether the user can delete the model. | ||||||
|      */ |      */ | ||||||
|  |  | ||||||
|  | @ -8,10 +8,9 @@ use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvid | ||||||
| 
 | 
 | ||||||
| class AuthServiceProvider extends ServiceProvider | class AuthServiceProvider extends ServiceProvider | ||||||
| { | { | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * The policy mappings for the application. |      * @inheritdoc | ||||||
|      * |  | ||||||
|      * @var array |  | ||||||
|      */ |      */ | ||||||
|     protected $policies = [ |     protected $policies = [ | ||||||
|         User::class => UserPolicy::class, |         User::class => UserPolicy::class, | ||||||
|  |  | ||||||
|  | @ -0,0 +1,70 @@ | ||||||
|  | <x-app-layout> | ||||||
|  |     <x-slot name="title">Edit Profile</x-slot> | ||||||
|  |     <x-slot name="header"> | ||||||
|  |         <h1 class="font-semibold text-xl text-gray-800 leading-tight">Edit Profile</h1> | ||||||
|  |     </x-slot> | ||||||
|  |     <form method="POST" enctype="multipart/form-data" action="{{ route('profiles.update', $user) }}"> | ||||||
|  |         @method('put') | ||||||
|  |         @csrf | ||||||
|  |         <div class="flex flex-col space-y-4"> | ||||||
|  |             <div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0"> | ||||||
|  |                 <!-- Username --> | ||||||
|  |                 <div class="flex-auto"> | ||||||
|  |                     <x-inputs.label for="username" value="Username"/> | ||||||
|  | 
 | ||||||
|  |                     <x-inputs.input name="username" | ||||||
|  |                                     type="text" | ||||||
|  |                                     class="block mt-1 w-full" | ||||||
|  |                                     autocapitalize="none" | ||||||
|  |                                     :value="old('username', $user->username)" | ||||||
|  |                                     :hasError="$errors->has('username')" | ||||||
|  |                                     required /> | ||||||
|  |                 </div> | ||||||
|  | 
 | ||||||
|  |                 <!-- Name --> | ||||||
|  |                 <div class="flex-auto"> | ||||||
|  |                     <x-inputs.label for="name" value="Display name"/> | ||||||
|  | 
 | ||||||
|  |                     <x-inputs.input name="name" | ||||||
|  |                                     type="text" | ||||||
|  |                                     class="block mt-1 w-full" | ||||||
|  |                                     :value="old('name', $user->name)"/> | ||||||
|  |                 </div> | ||||||
|  | 
 | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |             <div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0"> | ||||||
|  |                 <!-- Password --> | ||||||
|  |                 <div class="flex-auto"> | ||||||
|  |                     <x-inputs.label for="password" value="Password"/> | ||||||
|  | 
 | ||||||
|  |                     <x-inputs.input name="password" | ||||||
|  |                                     type="password" | ||||||
|  |                                     class="block mt-1 w-full" | ||||||
|  |                                     :hasError="$errors->has('password')" | ||||||
|  |                                     :required="!$user->exists"/> | ||||||
|  |                 </div> | ||||||
|  | 
 | ||||||
|  |                 <!-- Password confirm --> | ||||||
|  |                 <div class="flex-auto"> | ||||||
|  |                     <x-inputs.label for="password_confirmation" value="Confirm Password"/> | ||||||
|  | 
 | ||||||
|  |                     <x-inputs.input name="password_confirmation" | ||||||
|  |                                     type="password" | ||||||
|  |                                     class="block mt-1 w-full" | ||||||
|  |                                     :hasError="$errors->has('password')" | ||||||
|  |                                     :required="!$user->exists"/> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |             <!-- Image --> | ||||||
|  |             <div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0"> | ||||||
|  |                 <x-inputs.image :model="$user" preview_name="icon" /> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <div class="flex items-center justify-end mt-4"> | ||||||
|  |             <x-inputs.button>Save</x-inputs.button> | ||||||
|  |         </div> | ||||||
|  |     </form> | ||||||
|  | </x-app-layout> | ||||||
|  | @ -0,0 +1,9 @@ | ||||||
|  | <x-app-layout> | ||||||
|  |     <x-slot name="title">Profile</x-slot> | ||||||
|  |     <x-slot name="header"> | ||||||
|  |         <div class="flex justify-between items-center"> | ||||||
|  |             <h1 class="font-semibold text-2xl text-gray-800 leading-tight">Profile</h1> | ||||||
|  |         </div> | ||||||
|  |     </x-slot> | ||||||
|  |     {{ $user->name }} | ||||||
|  | </x-app-layout> | ||||||
|  | @ -4,6 +4,7 @@ use App\Http\Controllers\FoodController; | ||||||
| use App\Http\Controllers\GoalController; | use App\Http\Controllers\GoalController; | ||||||
| use App\Http\Controllers\IngredientPickerController; | use App\Http\Controllers\IngredientPickerController; | ||||||
| use App\Http\Controllers\JournalEntryController; | use App\Http\Controllers\JournalEntryController; | ||||||
|  | use App\Http\Controllers\ProfileController; | ||||||
| use App\Http\Controllers\RecipeController; | use App\Http\Controllers\RecipeController; | ||||||
| use App\Http\Controllers\Auth\AuthenticatedSessionController; | use App\Http\Controllers\Auth\AuthenticatedSessionController; | ||||||
| use Illuminate\Support\Facades\Route; | use Illuminate\Support\Facades\Route; | ||||||
|  | @ -15,7 +16,9 @@ use Illuminate\Support\Facades\Route; | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| Route::middleware(['auth'])->group(function () { | Route::middleware(['auth'])->group(function () { | ||||||
|  |     // Auth.
 | ||||||
|     Route::post('/logout', [AuthenticatedSessionController::class, 'destroy'])->name('logout'); |     Route::post('/logout', [AuthenticatedSessionController::class, 'destroy'])->name('logout'); | ||||||
|  | 
 | ||||||
|     // Foods.
 |     // Foods.
 | ||||||
|     Route::resource('foods', FoodController::class); |     Route::resource('foods', FoodController::class); | ||||||
|     Route::get('/foods/{food}/delete', [FoodController::class, 'delete'])->name('foods.delete'); |     Route::get('/foods/{food}/delete', [FoodController::class, 'delete'])->name('foods.delete'); | ||||||
|  | @ -36,4 +39,13 @@ Route::middleware(['auth'])->group(function () { | ||||||
|     // Recipes.
 |     // Recipes.
 | ||||||
|     Route::resource('recipes', RecipeController::class); |     Route::resource('recipes', RecipeController::class); | ||||||
|     Route::get('/recipes/{recipe}/delete', [RecipeController::class, 'delete'])->name('recipes.delete'); |     Route::get('/recipes/{recipe}/delete', [RecipeController::class, 'delete'])->name('recipes.delete'); | ||||||
|  | 
 | ||||||
|  |     // Users.
 | ||||||
|  |     Route::get('/profile/{user}', [ProfileController::class, 'show'])->name('profiles.show'); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | Route::middleware(['auth', 'can:editProfile,user'])->group(function () { | ||||||
|  |     // Profiles (non-admin Users variant).
 | ||||||
|  |     Route::get('/profile/{user}/edit', [ProfileController::class, 'edit'])->name('profiles.edit'); | ||||||
|  |     Route::put('/profile/{user}', [ProfileController::class, 'update'])->name('profiles.update'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue