Skip to content

Commit aba5670

Browse files
authored
Filament admin panel (#36)
* Install `filament/filament` * Set up User model to access admin panel * Set up admin pnael * Exclude Filament files from test coverage * Create UserStats.php * Create Filament User resource
1 parent e604f78 commit aba5670

File tree

12 files changed

+2495
-501
lines changed

12 files changed

+2495
-501
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
/public/vendor
44
/public/hot
55
/public/storage
6+
/public/css
7+
/public/js
68
/storage/*.key
79
/vendor
810
.env
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
namespace App\Filament\Resources;
4+
5+
use App\Filament\Resources\UserResource\Pages;
6+
use App\Models\User;
7+
use Filament\Forms;
8+
use Filament\Forms\Form;
9+
use Filament\Resources\Resource;
10+
use Filament\Tables;
11+
use Filament\Tables\Table;
12+
13+
class UserResource extends Resource
14+
{
15+
protected static ?string $model = User::class;
16+
17+
protected static ?string $navigationIcon = 'heroicon-o-users';
18+
19+
public static function form(Form $form): Form
20+
{
21+
return $form
22+
->schema([
23+
Forms\Components\TextInput::make('first_name')
24+
->autofocus()
25+
->required()
26+
->maxLength(255),
27+
28+
Forms\Components\TextInput::make('last_name')
29+
->required()
30+
->maxLength(255),
31+
32+
Forms\Components\TextInput::make('email')
33+
->required()
34+
->email()
35+
->maxLength(255),
36+
37+
Forms\Components\TextInput::make('password')
38+
->password()
39+
->maxLength(255),
40+
41+
Forms\Components\DateTimePicker::make('email_verified_at'),
42+
]);
43+
}
44+
45+
public static function table(Table $table): Table
46+
{
47+
return $table
48+
->columns([
49+
Tables\Columns\TextColumn::make('name')
50+
->getStateUsing(fn ($record) => $record->fullName)
51+
->searchable()
52+
->sortable(),
53+
54+
Tables\Columns\TextColumn::make('email')
55+
->searchable()
56+
->sortable(),
57+
58+
Tables\Columns\IconColumn::make('email_verified')
59+
->getStateUsing(fn ($record) => $record->email_verified_at)
60+
->boolean()
61+
->sortable(),
62+
63+
Tables\Columns\TextColumn::make('roles')
64+
->getStateUsing(fn ($record) => $record->roles->pluck('name')->join(', '))
65+
->searchable()
66+
->sortable(),
67+
68+
Tables\Columns\TextColumn::make('created_at')
69+
->sortable(),
70+
71+
Tables\Columns\TextColumn::make('updated_at')
72+
->sortable(),
73+
])
74+
->filters([
75+
//
76+
])
77+
->actions([
78+
Tables\Actions\EditAction::make(),
79+
])
80+
->bulkActions([
81+
Tables\Actions\BulkActionGroup::make([
82+
Tables\Actions\DeleteBulkAction::make(),
83+
]),
84+
]);
85+
}
86+
87+
public static function getRelations(): array
88+
{
89+
return [
90+
//
91+
];
92+
}
93+
94+
public static function getPages(): array
95+
{
96+
return [
97+
'index' => Pages\ListUsers::route('/'),
98+
'create' => Pages\CreateUser::route('/create'),
99+
'edit' => Pages\EditUser::route('/{record}/edit'),
100+
];
101+
}
102+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace App\Filament\Resources\UserResource\Pages;
4+
5+
use App\Filament\Resources\UserResource;
6+
use Filament\Resources\Pages\CreateRecord;
7+
8+
class CreateUser extends CreateRecord
9+
{
10+
protected static string $resource = UserResource::class;
11+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace App\Filament\Resources\UserResource\Pages;
4+
5+
use App\Filament\Resources\UserResource;
6+
use Filament\Actions;
7+
use Filament\Resources\Pages\EditRecord;
8+
9+
class EditUser extends EditRecord
10+
{
11+
protected static string $resource = UserResource::class;
12+
13+
protected function getHeaderActions(): array
14+
{
15+
return [
16+
Actions\DeleteAction::make(),
17+
];
18+
}
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace App\Filament\Resources\UserResource\Pages;
4+
5+
use App\Filament\Resources\UserResource;
6+
use Filament\Actions;
7+
use Filament\Resources\Pages\ListRecords;
8+
9+
class ListUsers extends ListRecords
10+
{
11+
protected static string $resource = UserResource::class;
12+
13+
protected function getHeaderActions(): array
14+
{
15+
return [
16+
Actions\CreateAction::make(),
17+
];
18+
}
19+
}

app/Filament/Widgets/UserStats.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace App\Filament\Widgets;
4+
5+
use App\Models\User;
6+
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
7+
use Filament\Widgets\StatsOverviewWidget\Stat;
8+
9+
class UserStats extends BaseWidget
10+
{
11+
protected function getStats(): array
12+
{
13+
return [
14+
Stat::make('Users', User::query()->count()),
15+
Stat::make('New users (last 7 days)', User::query()->where('created_at', '>=', \now()->subDays(7))->count()),
16+
Stat::make('New users (last 30 days)', User::query()->where('created_at', '>=', \now()->subDays(30))->count()),
17+
];
18+
}
19+
}

app/Models/User.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
// use Illuminate\Contracts\Auth\MustVerifyEmail;
66

7+
use Filament\Models\Contracts\FilamentUser;
8+
use Filament\Models\Contracts\HasName;
9+
use Filament\Panel;
710
use Illuminate\Database\Eloquent\Casts\Attribute;
811
use Illuminate\Database\Eloquent\Factories\HasFactory;
912
use Illuminate\Foundation\Auth\User as Authenticatable;
@@ -12,7 +15,7 @@
1215
use Laravel\Sanctum\HasApiTokens;
1316
use Spatie\Permission\Traits\HasRoles;
1417

15-
class User extends Authenticatable
18+
class User extends Authenticatable implements FilamentUser, HasName
1619
{
1720
use HasApiTokens;
1821
use HasFactory;
@@ -41,6 +44,13 @@ protected function password(): Attribute
4144
);
4245
}
4346

47+
protected function fullName(): Attribute
48+
{
49+
return Attribute::make(
50+
get: fn () => \trim($this->first_name.' '.$this->last_name),
51+
);
52+
}
53+
4454
protected function allPermissions(): Attribute
4555
{
4656
return Attribute::make(
@@ -66,4 +76,14 @@ public function updatePassword(?string $new_password = '')
6676
$this->save();
6777
}
6878
}
79+
80+
public function canAccessPanel(Panel $panel): bool
81+
{
82+
return $this->hasRole('admin');
83+
}
84+
85+
public function getFilamentName(): string
86+
{
87+
return $this->fullName;
88+
}
6989
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace App\Providers\Filament;
4+
5+
use Filament\Http\Middleware\Authenticate;
6+
use Filament\Http\Middleware\DisableBladeIconComponents;
7+
use Filament\Http\Middleware\DispatchServingFilamentEvent;
8+
use Filament\Pages;
9+
use Filament\Panel;
10+
use Filament\PanelProvider;
11+
use Filament\Support\Colors\Color;
12+
use Filament\Widgets;
13+
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
14+
use Illuminate\Cookie\Middleware\EncryptCookies;
15+
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
16+
use Illuminate\Routing\Middleware\SubstituteBindings;
17+
use Illuminate\Session\Middleware\AuthenticateSession;
18+
use Illuminate\Session\Middleware\StartSession;
19+
use Illuminate\View\Middleware\ShareErrorsFromSession;
20+
21+
class AdminPanelProvider extends PanelProvider
22+
{
23+
public function panel(Panel $panel): Panel
24+
{
25+
return $panel
26+
->default()
27+
->id('admin')
28+
->path('admin')
29+
->login()
30+
->colors([
31+
'primary' => Color::Amber,
32+
])
33+
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
34+
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
35+
->pages([
36+
Pages\Dashboard::class,
37+
])
38+
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
39+
->widgets([
40+
Widgets\AccountWidget::class,
41+
Widgets\FilamentInfoWidget::class,
42+
])
43+
->middleware([
44+
EncryptCookies::class,
45+
AddQueuedCookiesToResponse::class,
46+
StartSession::class,
47+
AuthenticateSession::class,
48+
ShareErrorsFromSession::class,
49+
VerifyCsrfToken::class,
50+
SubstituteBindings::class,
51+
DisableBladeIconComponents::class,
52+
DispatchServingFilamentEvent::class,
53+
])
54+
->authMiddleware([
55+
Authenticate::class,
56+
]);
57+
}
58+
}

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"license": "MIT",
66
"require": {
77
"php": "^8.2",
8+
"filament/filament": "^3.0",
89
"guzzlehttp/guzzle": "^7.8",
910
"inertiajs/inertia-laravel": "^0.6",
1011
"laravel/framework": "^10.24",
@@ -78,7 +79,8 @@
7879
],
7980
"post-autoload-dump": [
8081
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
81-
"@php artisan package:discover --ansi"
82+
"@php artisan package:discover --ansi",
83+
"@php artisan filament:upgrade"
8284
],
8385
"post-update-cmd": [
8486
"@php artisan vendor:publish --tag=laravel-assets --ansi --force",

0 commit comments

Comments
 (0)