Skip to content

Commit 2742ec4

Browse files
authored
Filament improvements (#114)
* Use custom theme * Remove top bar * Fix user passwords not saving in Filament * Use badge column for user roles * Add filters to users table * Wrap user form in section * Update LoginControllerTest.php
1 parent fc86028 commit 2742ec4

File tree

13 files changed

+113
-78
lines changed

13 files changed

+113
-78
lines changed

app/Filament/Resources/Users/Pages/CreateUser.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,16 @@
44

55
use App\Filament\Resources\Users\UserResource;
66
use Filament\Resources\Pages\CreateRecord;
7+
use Illuminate\Support\Facades\Hash;
78

89
class CreateUser extends CreateRecord
910
{
1011
protected static string $resource = UserResource::class;
12+
13+
protected function mutateFormDataBeforeCreate(array $data): array
14+
{
15+
$data['password'] = Hash::make($data['password']);
16+
17+
return $data;
18+
}
1119
}

app/Filament/Resources/Users/Pages/EditUser.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use App\Filament\Resources\Users\UserResource;
66
use Filament\Actions\DeleteAction;
77
use Filament\Resources\Pages\EditRecord;
8+
use Illuminate\Support\Facades\Hash;
89

910
class EditUser extends EditRecord
1011
{
@@ -16,4 +17,15 @@ protected function getHeaderActions(): array
1617
DeleteAction::make(),
1718
];
1819
}
20+
21+
protected function mutateFormDataBeforeSave(array $data): array
22+
{
23+
$password = $data['password'] ?? null;
24+
25+
if ($password) {
26+
$data['password'] = Hash::make($password);
27+
}
28+
29+
return $data;
30+
}
1931
}

app/Filament/Resources/Users/Schemas/UserForm.php

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,50 @@
55
use Filament\Forms\Components\DateTimePicker;
66
use Filament\Forms\Components\Select;
77
use Filament\Forms\Components\TextInput;
8+
use Filament\Schemas\Components\Section;
89
use Filament\Schemas\Schema;
910

1011
class UserForm
1112
{
1213
public static function configure(Schema $schema): Schema
1314
{
14-
return $schema
15+
return $schema->columns(6)
1516
->components([
16-
TextInput::make('first_name')
17-
->autofocus()
18-
->required()
19-
->maxLength(255),
20-
21-
TextInput::make('last_name')
22-
->required()
23-
->maxLength(255),
24-
25-
TextInput::make('email')
26-
->required()
27-
->email()
28-
->maxLength(255),
29-
30-
TextInput::make('password')
31-
->required(fn (string $operation): bool => $operation === 'create')
32-
->password()
33-
->afterStateHydrated(function (TextInput $component, $state) {
34-
$component->state('');
35-
})
36-
->dehydrated(fn (?string $state): bool => filled($state))
37-
->maxLength(255),
38-
39-
DateTimePicker::make('email_verified_at'),
40-
41-
Select::make('roles')
42-
->preload()
43-
->multiple()
44-
->relationship('roles', 'name'),
17+
18+
Section::make('Settings')
19+
->columnSpanFull()
20+
->columns(2)
21+
->schema([
22+
TextInput::make('first_name')
23+
->autofocus()
24+
->required()
25+
->maxLength(255),
26+
27+
TextInput::make('last_name')
28+
->required()
29+
->maxLength(255),
30+
31+
TextInput::make('email')
32+
->required()
33+
->email()
34+
->maxLength(255),
35+
36+
TextInput::make('password')
37+
->required(fn (string $operation): bool => $operation === 'create')
38+
->password()
39+
->afterStateHydrated(function (TextInput $component, $state) {
40+
$component->state('');
41+
})
42+
->dehydrated(fn (?string $state): bool => filled($state))
43+
->maxLength(255),
44+
45+
DateTimePicker::make('email_verified_at'),
46+
47+
Select::make('roles')
48+
->preload()
49+
->multiple()
50+
->relationship('roles', 'name'),
51+
]),
4552
]);
4653
}
4754
}

app/Filament/Resources/Users/Tables/UsersTable.php

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22

33
namespace App\Filament\Resources\Users\Tables;
44

5+
use App\Enums\Role;
6+
use App\Models\User;
57
use Filament\Actions\BulkActionGroup;
68
use Filament\Actions\DeleteBulkAction;
79
use Filament\Actions\EditAction;
8-
use Filament\Tables\Columns\IconColumn;
10+
use Filament\Support\Icons\Heroicon;
911
use Filament\Tables\Columns\TextColumn;
12+
use Filament\Tables\Filters\SelectFilter;
13+
use Filament\Tables\Filters\TernaryFilter;
1014
use Filament\Tables\Table;
1115

1216
class UsersTable
@@ -21,28 +25,46 @@ public static function configure(Table $table): Table
2125
->sortable(),
2226

2327
TextColumn::make('email')
28+
->tooltip(fn (User $user) => $user->hasVerifiedEmail() ? 'Email Verified' : 'Email Not Verified')
29+
->icon(fn (User $user) => match ($user->hasVerifiedEmail()) {
30+
true => Heroicon::OutlinedCheckCircle,
31+
false => Heroicon::OutlinedXCircle,
32+
})
33+
->iconColor(fn (User $user) => match ($user->hasVerifiedEmail()) {
34+
true => 'success',
35+
false => 'danger',
36+
})
2437
->searchable()
2538
->sortable(),
2639

27-
IconColumn::make('email_verified')
28-
->getStateUsing(fn ($record) => $record->email_verified_at)
29-
->boolean()
30-
->sortable(),
31-
3240
TextColumn::make('roles')
33-
->getStateUsing(fn ($record) => $record->roles->pluck('name')->join(', '))
41+
->badge()
42+
->getStateUsing(fn ($record) => $record->roles->pluck('name'))
3443
->searchable()
3544
->sortable(),
3645

3746
TextColumn::make('created_at')
38-
->dateTime()
39-
->sortable()
40-
->toggleable(isToggledHiddenByDefault: true),
47+
->toggleable(isToggledHiddenByDefault: true)
48+
->sortable(),
4149

4250
TextColumn::make('updated_at')
43-
->dateTime()
44-
->sortable()
45-
->toggleable(isToggledHiddenByDefault: true),
51+
->toggleable(isToggledHiddenByDefault: true)
52+
->sortable(),
53+
])
54+
->filters([
55+
TernaryFilter::make('email_verified_at')
56+
->label('Email Verification')
57+
->nullable()
58+
->placeholder('All')
59+
->trueLabel('Verified')
60+
->falseLabel('Unverified'),
61+
62+
SelectFilter::make('roles')
63+
->label('Roles')
64+
->relationship('roles', 'name')
65+
->options(Role::values())
66+
->multiple()
67+
->preload(),
4668
])
4769
->recordActions([
4870
EditAction::make(),

app/Http/Controllers/RegisterController.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use App\Http\Requests\Register\RegisterStoreRequest;
88
use App\Models\User;
99
use Filament\Auth\Events\Registered;
10+
use Illuminate\Support\Facades\Hash;
1011

1112
class RegisterController extends Controller
1213
{
@@ -24,7 +25,7 @@ public function store(RegisterStoreRequest $request)
2425
{
2526
$user = new User($request->only('first_name', 'last_name', 'email'));
2627

27-
$user->password = $request->validated('password');
28+
$user->password = Hash::make($request->validated('password'));
2829
$user->save();
2930

3031
$user->assignRole(Role::USER->value);

app/Http/Controllers/ResetPasswordController.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use App\Models\User;
88
use Illuminate\Auth\Events\PasswordReset;
99
use Illuminate\Http\Request;
10+
use Illuminate\Support\Facades\Hash;
1011
use Illuminate\Support\Facades\Password;
1112
use Illuminate\Support\Str;
1213
use Illuminate\Validation\ValidationException;
@@ -43,9 +44,9 @@ public function edit(Request $request, string $token)
4344

4445
public function update(ResetPasswordUpdateRequest $request)
4546
{
46-
$status = Password::reset($request->only('token', 'email', 'password', 'token'), function (User $user, string $password) {
47+
$status = Password::reset($request->only('token', 'email', 'password', 'password_confirmation'), function (User $user, string $password) {
4748
$user->forceFill([
48-
'password' => $password,
49+
'password' => Hash::make($password),
4950
])->setRememberToken(Str::random(60));
5051

5152
$user->save();

app/Models/User.php

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,6 @@ protected function casts(): array
4242
];
4343
}
4444

45-
protected function password(): Attribute
46-
{
47-
return Attribute::make(
48-
set: fn ($value) => Hash::make($value),
49-
);
50-
}
51-
5245
protected function fullName(): Attribute
5346
{
5447
return Attribute::make(
@@ -71,7 +64,7 @@ protected function scopeHasRoles(Builder $query, array $roles): void
7164
public function updatePassword(?string $new_password = '')
7265
{
7366
if ($new_password && $new_password != $this->password) {
74-
$this->password = $new_password;
67+
$this->password = Hash::make($new_password);
7568

7669
$this->save();
7770
}

app/Providers/Filament/AdminPanelProvider.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ public function panel(Panel $panel): Panel
2525
->default()
2626
->id('admin')
2727
->path('admin')
28-
->login(Login::class)
28+
->topbar(false)
29+
->viteTheme('resources/css/filament/admin/theme.css')
2930
->colors([
3031
'primary' => '#1e293b',
3132
])
33+
->login(Login::class)
3234
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
3335
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
3436
->pages([
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@import '../../../../vendor/filament/filament/resources/css/theme.css';
2+
3+
@source '../../../../app/Filament/**/*';
4+
@source '../../../../resources/views/filament/**/*';

tests/Feature/Controllers/LoginControllerTest.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use App\Enums\Environment;
44
use App\Models\User;
5+
use Illuminate\Support\Facades\Hash;
56
use Inertia\Testing\AssertableInertia as Assert;
67

78
use function Pest\Faker\fake;
@@ -54,7 +55,7 @@
5455
test('Can login', function () {
5556
$password = fake()->password();
5657
$user = User::factory()->create([
57-
'password' => $password,
58+
'password' => Hash::make($password),
5859
]);
5960

6061
assertGuest();
@@ -72,7 +73,7 @@
7273
test('Can be redirected after login', function () {
7374
$password = fake()->password();
7475
$user = User::factory()->create([
75-
'password' => $password,
76+
'password' => Hash::make($password),
7677
]);
7778

7879
$redirectUrl = 'https://www.google.com/';
@@ -93,7 +94,7 @@
9394

9495
test("Can't login with invalid credentials", function () {
9596
$user = User::factory()->create([
96-
'password' => fake()->password(),
97+
'password' => Hash::make(fake()->password()),
9798
]);
9899

99100
assertGuest();

0 commit comments

Comments
 (0)