mirror of
https://github.com/sp-tarkov/forge.git
synced 2025-02-12 12:10:41 -05:00
Adds Cover Photo Field
Adds the cover photo field to the Jetstream edit profile form.
This commit is contained in:
parent
55273e5a90
commit
35cd00e39d
@ -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);
|
||||
|
76
app/Livewire/Profile/UpdateProfileForm.php
Normal file
76
app/Livewire/Profile/UpdateProfileForm.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Profile;
|
||||
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||
use Laravel\Jetstream\Http\Livewire\UpdateProfileInformationForm;
|
||||
use Livewire\Features\SupportRedirects\Redirector;
|
||||
|
||||
class UpdateProfileForm extends UpdateProfileInformationForm
|
||||
{
|
||||
/**
|
||||
* The new cover photo for the user.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $cover;
|
||||
|
||||
/**
|
||||
* When the photo is temporarily uploaded.
|
||||
*/
|
||||
public function updatedPhoto(): void
|
||||
{
|
||||
$this->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');
|
||||
}
|
||||
}
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
72
app/Traits/HasCoverPhoto.php
Normal file
72
app/Traits/HasCoverPhoto.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
trait HasCoverPhoto
|
||||
{
|
||||
/**
|
||||
* Update the user's cover photo.
|
||||
*/
|
||||
public function updateCoverPhoto(UploadedFile $cover, $storagePath = 'cover-photos'): void
|
||||
{
|
||||
tap($this->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';
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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,
|
||||
],
|
||||
|
||||
/*
|
||||
|
@ -8,7 +8,7 @@
|
||||
<div>
|
||||
<div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8">
|
||||
@if (Laravel\Fortify\Features::canUpdateProfileInformation())
|
||||
@livewire('profile.update-profile-information-form')
|
||||
@livewire('profile.update-profile-form')
|
||||
|
||||
<x-section-border />
|
||||
@endif
|
||||
|
@ -8,7 +8,7 @@
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="form">
|
||||
<!-- Profile Photo -->
|
||||
<!-- Profile Picture -->
|
||||
@if (Laravel\Jetstream\Jetstream::managesProfilePhotos())
|
||||
<div x-data="{photoName: null, photoPreview: null}" class="col-span-6 sm:col-span-4">
|
||||
<!-- Profile Photo File Input -->
|
||||
@ -24,7 +24,7 @@
|
||||
reader.readAsDataURL($refs.photo.files[0]);
|
||||
" />
|
||||
|
||||
<x-label for="photo" value="{{ __('Photo') }}" />
|
||||
<x-label for="photo" value="{{ __('Profile Picture') }}" />
|
||||
|
||||
<!-- Current Profile Photo -->
|
||||
<div class="mt-2" x-show="! photoPreview">
|
||||
@ -52,6 +52,48 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Cover Picture -->
|
||||
<div x-data="{coverName: null, coverPreview: null}" class="col-span-6 sm:col-span-4">
|
||||
<!-- Cover Picture File Input -->
|
||||
<input type="file" id="cover" class="hidden"
|
||||
wire:model.live="cover"
|
||||
x-ref="cover"
|
||||
x-on:change="
|
||||
coverName = $refs.cover.files[0].name;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
coverPreview = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL($refs.cover.files[0]);
|
||||
" />
|
||||
|
||||
<x-label for="cover" value="{{ __('Cover Picture') }}" />
|
||||
|
||||
<!-- Current Cover Photo -->
|
||||
<div class="mt-2" x-show="! coverPreview">
|
||||
<img src="{{ $this->user->cover_photo_url }}" alt="{{ $this->user->name }}" class="rounded-sm h-20 w-60 object-cover">
|
||||
</div>
|
||||
|
||||
<!-- New Cover Photo Preview -->
|
||||
<div class="mt-2" x-show="coverPreview" style="display: none;">
|
||||
<span class="block h-20 w-60 bg-cover bg-no-repeat bg-center"
|
||||
x-bind:style="'background-image: url(\'' + coverPreview + '\');'">
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<x-secondary-button class="mt-2 me-2" type="button" x-on:click.prevent="$refs.cover.click()">
|
||||
{{ __('Select A New Cover Photo') }}
|
||||
</x-secondary-button>
|
||||
|
||||
@if ($this->user->cover_photo_path)
|
||||
<x-secondary-button type="button" class="mt-2" wire:click="deleteCoverPhoto">
|
||||
{{ __('Remove Cover Photo') }}
|
||||
</x-secondary-button>
|
||||
@endif
|
||||
|
||||
<x-input-error for="cover" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Name -->
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
<x-label for="name" value="{{ __('Name') }}" />
|
||||
@ -88,7 +130,7 @@
|
||||
{{ __('Saved.') }}
|
||||
</x-action-message>
|
||||
|
||||
<x-button wire:loading.attr="disabled" wire:target="photo">
|
||||
<x-button wire:loading.attr="disabled" wire:target="photo,cover">
|
||||
{{ __('Save') }}
|
||||
</x-button>
|
||||
</x-slot>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<div class="sm:-mt-12 dark:bg-gray-800 dark:text-gray-100">
|
||||
<div>
|
||||
<img class="h-32 w-full object-cover lg:h-48" src="https://images.unsplash.com/photo-1444628838545-ac4016a5418a?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1950&q=80" alt="">
|
||||
<img class="h-32 w-full object-cover lg:h-48" src="{{ $user->cover_photo_url }}" alt="{{ $user->name }}">
|
||||
</div>
|
||||
<div class="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
|
||||
<div class="-mt-12 sm:-mt-16 sm:flex sm:items-end sm:space-x-5">
|
||||
|
Loading…
x
Reference in New Issue
Block a user