diff --git a/app/Actions/Fortify/UpdateUserProfileInformation.php b/app/Actions/Fortify/UpdateUserProfileInformation.php index 9738772..10569dd 100644 --- a/app/Actions/Fortify/UpdateUserProfileInformation.php +++ b/app/Actions/Fortify/UpdateUserProfileInformation.php @@ -21,12 +21,17 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)], 'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'], + 'cover' => ['nullable', 'mimes:jpg,jpeg,png', 'max:2048'], ])->validateWithBag('updateProfileInformation'); if (isset($input['photo'])) { $user->updateProfilePhoto($input['photo']); } + if (isset($input['cover'])) { + $user->updateCoverPhoto($input['cover']); + } + if ($input['email'] !== $user->email && $user instanceof MustVerifyEmail) { $this->updateVerifiedUser($user, $input); diff --git a/app/Livewire/Profile/UpdateProfileForm.php b/app/Livewire/Profile/UpdateProfileForm.php new file mode 100644 index 0000000..127e34f --- /dev/null +++ b/app/Livewire/Profile/UpdateProfileForm.php @@ -0,0 +1,76 @@ +validate([ + 'photo' => 'image|mimes:jpg,jpeg,png|max:1024', // 1MB Max + ]); + } + + /** + * When the cover is temporarily uploaded. + */ + public function updatedCover(): void + { + $this->validate([ + 'cover' => 'image|mimes:jpg,jpeg,png|max:2048', // 2MB Max + ]); + } + + /** + * Update the user's profile information. + */ + public function updateProfileInformation(UpdatesUserProfileInformation $updater): RedirectResponse|Redirector|null + { + $this->resetErrorBag(); + + $updater->update( + Auth::user(), + $this->photo || $this->cover + ? array_merge($this->state, array_filter([ + 'photo' => $this->photo, + 'cover' => $this->cover, + ])) : $this->state + ); + + if (isset($this->photo) || isset($this->cover)) { + return redirect()->route('profile.show'); + } + + $this->dispatch('saved'); + + $this->dispatch('refresh-navigation-menu'); + + return null; + } + + /** + * Delete user's profile photo. + */ + public function deleteCoverPhoto(): void + { + Auth::user()->deleteCoverPhoto(); + + $this->dispatch('refresh-navigation-menu'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 26b0598..bfbfa90 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,6 +4,7 @@ namespace App\Models; use App\Notifications\ResetPassword; use App\Notifications\VerifyEmail; +use App\Traits\HasCoverPhoto; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -21,6 +22,7 @@ class User extends Authenticatable implements MustVerifyEmail { use Bannable; use HasApiTokens; + use HasCoverPhoto; use HasFactory; use HasProfilePhoto; use Notifiable; @@ -134,6 +136,14 @@ class User extends Authenticatable implements MustVerifyEmail return $this->belongsTo(UserRole::class, 'user_role_id'); } + /** + * Get the disk that profile photos should be stored on. + */ + protected function profilePhotoDisk(): string + { + return config('filesystems.asset_upload', 'public'); + } + /** * The attributes that should be cast to native types. */ @@ -144,15 +154,4 @@ class User extends Authenticatable implements MustVerifyEmail 'password' => 'hashed', ]; } - - /** - * Get the disk that profile photos should be stored on. - */ - protected function profilePhotoDisk(): string - { - return match (config('app.env')) { - 'production' => 'r2', // Cloudflare R2 Storage - default => 'public', // Local - }; - } } diff --git a/app/Traits/HasCoverPhoto.php b/app/Traits/HasCoverPhoto.php new file mode 100644 index 0000000..ce429e3 --- /dev/null +++ b/app/Traits/HasCoverPhoto.php @@ -0,0 +1,72 @@ +cover_photo_path, function ($previous) use ($cover, $storagePath) { + $this->forceFill([ + 'cover_photo_path' => $cover->storePublicly( + $storagePath, ['disk' => $this->coverPhotoDisk()] + ), + ])->save(); + + if ($previous) { + Storage::disk($this->coverPhotoDisk())->delete($previous); + } + }); + } + + /** + * Get the disk that cover photos should be stored on. + */ + protected function coverPhotoDisk(): string + { + return config('filesystems.asset_upload', 'public'); + } + + /** + * Delete the user's cover photo. + */ + public function deleteCoverPhoto(): void + { + if (is_null($this->cover_photo_path)) { + return; + } + + Storage::disk($this->coverPhotoDisk())->delete($this->cover_photo_path); + + $this->forceFill([ + 'cover_photo_path' => null, + ])->save(); + } + + /** + * Get the URL to the user's cover photo. + */ + public function coverPhotoUrl(): Attribute + { + return Attribute::get(function (): string { + return $this->cover_photo_path + ? Storage::disk($this->coverPhotoDisk())->url($this->cover_photo_path) + : $this->defaultCoverPhotoUrl(); + }); + } + + /** + * Get the default profile photo URL if no profile photo has been uploaded. + */ + protected function defaultCoverPhotoUrl(): string + { + return 'https://picsum.photos/seed/'.urlencode($this->name).'/720/100?blur=2'; + } +} diff --git a/config/filesystems.php b/config/filesystems.php index f51b156..62d033d 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -15,6 +15,18 @@ return [ 'default' => env('FILESYSTEM_DISK', 'local'), + /* + |-------------------------------------------------------------------------- + | Default Asset Upload Disk + |-------------------------------------------------------------------------- + | + | Here you may specify the default filesystem disk that assets should be + | uploaded to. Typically, this will be either the "public" or "r2" disk. + | + */ + + 'asset_upload' => env('ASSET_UPLOAD_DISK', 'public'), + /* |-------------------------------------------------------------------------- | Filesystem Disks diff --git a/config/livewire.php b/config/livewire.php index 0d2ba89..c8bd1cd 100644 --- a/config/livewire.php +++ b/config/livewire.php @@ -64,17 +64,17 @@ return [ */ 'temporary_file_upload' => [ - 'disk' => null, // Example: 'local', 's3' | Default: 'default' - 'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB) - 'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp' - 'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1' - 'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs... + 'disk' => null, + 'rules' => ['file', 'max:12288'], + 'directory' => null, + 'middleware' => 'throttle:5,1', + 'preview_mimes' => [ 'png', 'gif', 'bmp', 'svg', 'wav', 'mp4', 'mov', 'avi', 'wmv', 'mp3', 'm4a', 'jpg', 'jpeg', 'mpga', 'webp', 'wma', ], - 'max_upload_time' => 5, // Max duration (in minutes) before an upload is invalidated... - 'cleanup' => true, // Should cleanup temporary uploads older than 24 hrs... + 'max_upload_time' => 5, + 'cleanup' => true, ], /* diff --git a/resources/views/profile/show.blade.php b/resources/views/profile/show.blade.php index a089e25..76bbc77 100644 --- a/resources/views/profile/show.blade.php +++ b/resources/views/profile/show.blade.php @@ -8,7 +8,7 @@
@if (Laravel\Fortify\Features::canUpdateProfileInformation()) - @livewire('profile.update-profile-information-form') + @livewire('profile.update-profile-form') @endif diff --git a/resources/views/profile/update-profile-information-form.blade.php b/resources/views/profile/update-profile-information-form.blade.php index 57e560f..74b0f75 100644 --- a/resources/views/profile/update-profile-information-form.blade.php +++ b/resources/views/profile/update-profile-information-form.blade.php @@ -8,7 +8,7 @@ - + @if (Laravel\Jetstream\Jetstream::managesProfilePhotos())
@@ -24,7 +24,7 @@ reader.readAsDataURL($refs.photo.files[0]); " /> - +
@@ -52,6 +52,48 @@
@endif + +
+ + + + + + +
+ {{ $this->user->name }} +
+ + + + + + {{ __('Select A New Cover Photo') }} + + + @if ($this->user->cover_photo_path) + + {{ __('Remove Cover Photo') }} + + @endif + + +
+
@@ -88,7 +130,7 @@ {{ __('Saved.') }} - + {{ __('Save') }} diff --git a/resources/views/user/show.blade.php b/resources/views/user/show.blade.php index 943e592..8e2874c 100644 --- a/resources/views/user/show.blade.php +++ b/resources/views/user/show.blade.php @@ -2,7 +2,7 @@
- + {{ $user->name }}