More Profile Feature WIP. Next up - tests

This commit is contained in:
Ryan Scherler
2020-12-01 17:47:06 -08:00
parent d6ff0e6c32
commit 8b68e73539
19 changed files with 128 additions and 67 deletions
+1 -2
View File
@@ -17,7 +17,6 @@ return [
'features' => [
Features::registration(),
// Features::profile(),
// Features::permissions(),
Features::resources(),
],
@@ -138,7 +137,7 @@ return [
*/
'cache_disk' => env('FILAMENT_CACHE_DISK', 'local'),
'cache_path' => '.cache',
'cache_path' => 'filament-tmp',
/*
|--------------------------------------------------------------------------
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,6 +1,6 @@
{
"/js/filament.js": "/js/filament.js?id=9995e728ce8648b81f5d",
"/css/filament.css": "/css/filament.css?id=154a0f553a2b1539829a",
"/css/filament.css": "/css/filament.css?id=59ae2ccb0284510a04e7",
"/js/filament.js.map": "/js/filament.js.map?id=1d3615b89069bfd79261",
"/css/filament.css.map": "/css/filament.css.map?id=227394566b95aa646612"
"/css/filament.css.map": "/css/filament.css.map?id=e875f899bca9d385fc36"
}
+2 -2
View File
@@ -1,8 +1,8 @@
.btn {
@apply text-sm font-medium py-2 px-4 bg-gray-100 text-gray-600 rounded transition duration-200 shadow-sm inline-block relative focus:ring focus:ring-indigo-200 focus:ring-opacity-50 hover:bg-gray-200;
@apply cursor-pointer text-sm font-semibold py-2 px-4 border border-gray-300 bg-gray-100 text-gray-800 rounded transition duration-200 shadow-sm inline-block relative focus:ring focus:ring-indigo-200 focus:ring-opacity-50 hover:bg-gray-200;
&-primary {
@apply bg-blue-700 text-white hover:bg-blue-800;
@apply border-transparent bg-blue-700 text-white hover:bg-blue-800;
}
&[disabled] {
+1
View File
@@ -13,4 +13,5 @@
}
@layer utilities {
@import './utilities/typography.css';
}
+12
View File
@@ -0,0 +1,12 @@
.help,
.hint {
@apply text-xs font-normal leading-tight text-gray-500;
a {
@apply text-blue-600 hover:text-blue-800;
}
}
.hint {
@apply font-mono;
}
+1 -1
View File
@@ -1,3 +1,3 @@
<span {{ $attributes->merge(['class' => 'text-xs font-normal leading-tight text-gray-500']) }}>
<span {{ $attributes->merge(['class' => 'help']) }}>
{{ $slot }}
</span>
+1 -1
View File
@@ -1,3 +1,3 @@
<span {{ $attributes->merge(['class' => 'font-normal font-mono text-xs leading-tight text-gray-500']) }}>
<span {{ $attributes->merge(['class' => 'hint']) }}>
{{ $slot }}
</span>
@@ -0,0 +1,42 @@
@props([
'field',
'avatar',
'user',
'size' => 64,
'class' => '',
'delete' => null,
'id',
])
<div x-cloak
x-data="{ isUploading: false, progress: 0 }"
x-on:livewire-upload-start="isUploading = true"
x-on:livewire-upload-finish="isUploading = false"
x-on:livewire-upload-error="isUploading = false"
x-on:livewire-upload-progress="progress = $event.detail.progress"
class="flex items-center space-x-4">
<div class="flex-shrink-0 relative">
<label for="{{ $id }}" class="cursor-pointer rounded-full shadow flex overflow-hidden {{ $class }}">
@if ($avatar)
<img src="{{ $avatar->temporaryUrl() }}" alt="{{ $user->name }}" class="h-full object-cover" width="{{ $size }}" height="{{ $size }}" loading="lazy">
@else
<x-filament-avatar :size="$size" :user="$user" />
@endif
</label>
@if ($user->avatar && $delete)
<button type="button"
wire:click="{{ $delete }}"
class="absolute top-0 right-0 w-4 h-4 rounded-full bg-gray-800 text-white flex items-center justify-center hover:bg-red-700">
<span class="sr-only">{{ __('Delete Avatar') }}</span>
<x-heroicon-o-x class="w-3 h-3" />
</button>
@endif
</div>
<div class="flex-grow relative">
<label class="btn" for="{{ $id }}">{{ __('Change') }}</label>
<input type="file" wire:model="{{ $field }}" class="sr-only" id="{{ $id }}" {{ $attributes }}>
<div x-show="isUploading" :aria-hidden="!isUploading" class="absolute bottom-0 -mb-3 w-48 max-w-full">
<x-filament::progress progress="progress" />
</div>
</div>
</div>
@@ -0,0 +1,10 @@
@props([
'max' => 100,
'progress',
])
<div {{ $attributes }}>
<progress max="{{ $max }}" x-bind:value="{{ $progress }}" class="sr-only"></progress>
<div class="rounded-full border border-gray-200 overflow-hidden" aria-hidden="true">
<div class="h-1 bg-gray-500" :style="`width: ${ {{ $progress }} }%`"></div>
</div>
</div>
+2 -7
View File
@@ -7,16 +7,11 @@
<x-filament::input type="email" field="user.email" wire:model.lazy="user.email" id="email" required autocomplete="email" tabindex="2" />
</x-filament::input-group-stacked>
<x-filament::input-group-stacked field="avatar" for="photo" :label="__('Photo')">
<div>
<input type="file" wire:model="avatar" id="photo" tabindex="3" />
</div>
<x-filament::input-group-stacked field="avatar" for="photo" :label="__('Avatar')">
<x-filament::input-avatar field="avatar" :avatar="$avatar" :user="$user" delete="deleteAvatar" id="photo" tabindex="3" />
<x-slot name="hint">
{{ __('Optional') }}
</x-slot>
<x-slot name="help">
{{ __('Your user avatar photo.') }}
</x-slot>
</x-filament::input-group-stacked>
<x-filament::fieldset :legend="__('Update Password')" cols="2">
@@ -3,7 +3,7 @@
<x-filament::input type="email" field="email" wire:model.defer="email" id="email" required autocomplete="email" autofocus tabindex="1" />
@if (Route::has('filament.register'))
<x-slot name="hint">
<a href="{{ route('filament.register') }}" class="text-current hover:text-blue-600" tabindex="6">
<a href="{{ route('filament.register') }}" tabindex="6">
{{ __('filament::auth.register') }}
</a>
</x-slot>
@@ -12,7 +12,7 @@
<x-filament::input-group-stacked field="password" for="password" :label="__('Password')" required>
<x-slot name="hint">
<a href="{{ route('filament.password.forgot') }}" class="text-current hover:text-blue-600" tabindex="5">
<a href="{{ route('filament.password.forgot') }}" tabindex="5">
{{ __('Forgot Your Password?') }}
</a>
</x-slot>
@@ -3,18 +3,8 @@
<x-filament::tab id="account">
{{ __('Account') }}
</x-filament::tab>
@if (Filament\Features::hasPermissions())
<x-filament::tab id="permissions">
{{ __('Permissions') }}
</x-filament::tab>
@endif
</x-slot>
<x-filament::tab-panel id="account">
<livewire:filament-account :user="Auth::user()" />
</x-filament::tab-panel>
@if (Filament\Features::hasPermissions())
<x-filament::tab-panel id="permissions">
Permissions...
</x-filament::tab-panel>
@endif
</x-filament::tabs>
-20
View File
@@ -45,16 +45,6 @@ class Features
return static::enabled(static::resources());
}
/**
* Determine if the application has user permissions.
*
* @return bool
*/
public static function hasPermissions()
{
return static::enabled(static::permissions());
}
/**
* Enable the user registration feature.
*
@@ -75,16 +65,6 @@ class Features
return 'profile';
}
/**
* Enable the user permissions feature.
*
* @return string
*/
public static function permissions()
{
return 'permissions';
}
/**
* Enable the resources feature.
*
+33 -3
View File
@@ -5,7 +5,11 @@ namespace Filament\Http\Livewire;
use Livewire\Component;
use Livewire\WithFileUploads;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\{
Hash,
Storage,
};
class Account extends Component
{
@@ -22,7 +26,6 @@ class Account extends Component
'required',
'email',
],
'avatar' => 'nullable|image|max:20',
'password' => 'nullable',
];
@@ -39,12 +42,39 @@ class Account extends Component
]);
}
public function updatedAvatar($value)
{
$extension = pathinfo($value->getFilename(), PATHINFO_EXTENSION);
if (!in_array($extension, ['png', 'jpeg', 'bmp', 'gif'])) {
$this->reset('avatar');
}
$this->validate([
'avatar' => 'mimes:png,jpeg,bmp,gif|max:512', // .5MB Max
]);
}
public function deleteAvatar()
{
$avatar = $this->user->avatar;
if ($avatar) {
Storage::disk(config('filament.storage_disk'))->delete($avatar);
$this->avatar = null;
$this->user->avatar = null;
$this->user->save();
$this->dispatchBrowserEvent('notify', __('Avatar removed for :name', ['name' => $this->user->name]));
}
}
public function submit()
{
$this->validate();
if ($this->avatar) {
dd($this->avatar);
$this->user->avatar = $this->avatar->store('avatars', config('filament.storage_disk'));
}
if ($this->password) {
+4
View File
@@ -24,6 +24,10 @@ class Login extends Component
public function mount()
{
if (Auth::check()) {
return redirect()->to(Filament::home());
}
$this->message = session('message');
}
+5 -15
View File
@@ -15,15 +15,14 @@ class RouteServiceProvider extends ServiceProvider
parent::boot();
}
protected function prefix()
{
return config('filament.prefix.route');
}
public function map(): void
{
$this->mapWebRoutes();
$this->mapAliasRoutes();
}
protected function prefix()
{
return config('filament.prefix.route');
}
protected function mapWebRoutes(): void
@@ -35,13 +34,4 @@ class RouteServiceProvider extends ServiceProvider
->group(__DIR__.'/../../routes/web.php');
}
}
protected function mapAliasRoutes(): void
{
if (!Route::has('login')) {
Route::get('/login', function () {
return redirect()->route('filament.login');
})->name('login');
}
}
}
+8
View File
@@ -31,6 +31,14 @@ class LoginTest extends TestCase
$this->assertAuthenticated();
}
public function test_redirect_if_logged_in()
{
$user = User::factory()->create();
Livewire::actingAs($user)
->test(Login::class)
->assertRedirect(Filament::home());
}
public function test_email_is_required()
{
Livewire::test(Login::class)