Implementing a replacement of existing admin panel with Filament (6+ year old active SaaS project)
Hi Everyone, I'm going to be documenting my journey replacing an existing admin panel for my SaaS (https://spacecoders.com)
I'm treating this help post as sort of a blog post/ongoing thread, since I don't have a blog and would like to document my journey and my thought process in case it helps anyone else who's doing something similar.
My goal is to replace the entire admin section of the application with Filament. (and in the bring the admin section of the application up to the same standards/technologies as the rest of the application)
Hopefully I can edit this initial post and update this thread with relevant key progress updates or further backstory
Space Coders
Learn to code games and websites. Ages 9 to adult.
43 Replies
Backstory:
The current application has been heavily developed over the past 6 years to run the entire operations of my business.
When the Laravel application was first created, it was on Laravel 5.3, php 7.1, and auth was handled with laravel/ui. The application is currently running on Laravel 10, Livewire 3, and php 8.2 and laravel/ui is still in place.
The application is split into 5 different sections (domains/modules/folders) based on the scope of the user. eg. I have an admin section, a customer panel section, a student workspace section etc.. etc.. (for the purposes of this replacement, I'll only be working with the 'admin' section, but the other sections of the application need to continue working as expected, and filament might need to be configured to fit the current folder structure)
There are 5 custom guards, the default guard isn't
web
, and 5 models/tables for the different types of users. The only user that should have access to the admin panel is App\Models\Admin
The laravel/ui package with it's authentication is still in place, and has been extended to support multiple login endpoints depending on which user is trying to log in, has 2fa for some user types, and redirects to different home pages depending on which user guard is authenticated.
There are over 150 /admin/* routes in my application (most of which are a single Route::resource() line )
( ideally all of these will be deleted and replaced with Filament)
The current admin panel was started in 2018 using Material Bootstrap Pro from Creative Tim (https://www.creative-tim.com/product/material-dashboard-pro-bs4).
The admin styles are all using this template with static css/js in the public directory (the rest of the application has moved on and is using modern versions of tailwind, livewire, vue, vite asset compilation)
Goals
My goal is to replace the entire admin section of the application with Filament. (and in the bring the admin section of the application up to the same standards/technologies as the rest of the application)
Links to sections
- Login System
- Impersonation
- Themes, Fonts and Styling
Phase 1
- [x] get filament installed and configured in a new laravel app
- [x] replicate the existing app's core functionality in the new app
- [x] folder structure (css/js/assets)
- [x] dashboard path
- [x] guards
- [x] login logic
- [x] impersonation logic
- [x] theme, fonts and styling
- [ ] search logic
Phase 2
- [ ] get filament installed and configured in the existing laravel app
- [ ] custom dashboard widgets
- [ ] custom pages/partials
Todo List
(this is going to be updated at a high level as things progress, and will be a fairly loose stream of ideas)
- [x] set up a brand new laravel app with filament, and see if it can be configured to suit requirements
- [x] get it installed
- [x] figure out why it added css and js files to public/{js/css}/filament/*
- [x] can these be moved to a better location. eg. public/admin/assets/{js/css}/*
- [x] yes, publish and update filament.php config. with 'assets_path' => 'admin/assets'
- [x] can I customise the Filament namespacing/folders to match the existing app
- [x] css/js? no but you can put it in a subdirectory. public/admin/assets/{js/css}/filament/*
- [x] PanelProviders? yes: update config/app.php
to have App\Providers\AdminPanelProvider::class
(and move the panel provider and rename it's namespace)
- [x] Resources? yes: update ->discoverResources(in: app_path('Filament/Admin/Resources'), for: 'App\Filament\Admin\Resources')
- [x] Pages? yes: update ->discoverPages(in: app_path('Filament/Admin/Pages'), for: 'App\Filament\Admin\Pages')
- [x] Widgets? yes: update ->discoverWidgets(in: app_path('Filament/Admin/Widgets'), for: 'App\Filament\Admin\Widgets')
- [x] can I change the login screen to use the same one as current logic
- [x] maybe login using existing login form, get auth, get 2fa, then redirect to dashboard?
- [x] should I just remove the login logic from filament, it seems that login() is a function on the AdminPanelProvider
- [x] can I use the Admin guard instead of User
- [x] Link to Users in Filament Docs
- [x] Implement same structure with guards and models
- [x] logging in as Admin works
- [x] user impersonation
- [x] can I use the same impersonation logic as the existing app
- [x] can I change the Dashboard path to /admin/home instead of /admin as this doesn't work if the assets have been moved to public/admin
- [x] create a new dashboard page with the route /admin/home
- [x] learn what all the function calls do in AdminPanelProvider
- [x] syling and theming to match space theme
- [x] figure out how to get filament to compile logo styling
- [x] using a custom theme, I can specify the tailwind classes to include in the tailwind.config.js
config file
- [x] move css to js
- [x] create theme.js file, add import './theme.css';
- [x] works as hoped π
- [x] rename theme.css and theme.js to app.css and app.js
- [x] custom fonts
- [x] use @font-face in theme.css
- [x] update panel config to use new font
- [x] see if there's a way to disable the bunny fonts provider. Yes, use LocalFontProvider in panel
- [x] space themed background image
- [ ] custom dashboard widgets (compare screenshot examples)
- [ ] Weekly summary grid
- [ ] Enrollments Today/Yesterday
- [ ] Last 7 days new paid enrollments
- [ ] custom pages/partials
- [ ] Signup - Transactions section:
- [ ] View details
- [ ] Refund
- [ ] Receipt (PDF)
- [ ] Receipt (Email)
- [ ] Signup - Attendance section:
- [ ] View details
- [ ] Change Status
- [ ] Remove all lessons action
- [ ] Reschedule all remaining lessons action
- [ ] Reschedule single lesson (select new date/time in table)
Login System
Ok, let's work through this, the biggest deal breaker is going to be if I can't get the authentication system that Filament uses to play nice with the existing authentication system. I have multiple guards set up in my existing app, so let's start by replicating the config\auth.php
file:
The features that are essential are: going to /admin/login shows the admin login page, only App\Model\Admin users can access all the /admin routes (I'll auth as a different user type and then try hit an admin route to see what happens), the existing login system at /login still works for other user types, impersonation works, tests pass (various login checks, eg. none of the user types can access other areas), clean maintainable code
Nice to have but not essential: all the login pages share the same views, there's a single/shared common auth system for all types of users, can be upgraded to future versions of Filament in future without breaking changes
In reviewing the Filament Login page, there's an authenticate method that handles the authentication of the user. This resembles Laravel's standard authentication, which would be nice, since I might be able to just use Laravel's authentication and redirect to a Filament page and it "just works".. There are a few questions that I have about the Filament authenticate method that gives me pause though, so I need to understand how it works in more detail firstLooking at lines 73 and 77, I can see the authentication class is referenced as Filament::auth() which is very similar to the Laravel Auth, so hopefully Filament just extends Laravel Auth
Yes, it seems that my assumption was correct, Filament::auth() is using Laravel's StatefulGuard implementation under the hood
We now have a much bigger problem to solve.. it appears that I can't simply remove or disable the Filament login in the panel config. Removing this line or setting it to null throws an error
Now I'm a bit stuck.. I have 2 paths I could go down..
a. use Filament's login for the admin panel and implement all the additional functionality I need into it. (eg. 2fa, styling)
b. disable Filament's login for the admin panel and redirect to the Filament panel after authentication.
Let's try path B first as there seems to be a section on setting up guest access to a panel.. https://filamentphp.com/docs/3.x/panels/users#setting-up-guest-access-to-a-panel
I'm thinking the reason we hit that error page earlier is because I removed the ->login() method from the panel, but still have the Authenticate middleware on the panel, which is probably just redirecting to '/login' which I no longer have in my routes at all since it's a brand new project. Let's try adding the login routes in manually as they would exist in my actual application to see if this resolves the issue
Ok.. I got login working with my current login implementation. Let's walk through how it did it. (these steps can all be performed in any order since they all rely on each other anyway)
1. install the laravel/ui package
2. create get and post login routes that point to respective LoginController methods. eg.
3. implement the
use AuthenticatesUsers;
trait on the LoginController
4. implement LoginController methods for auth (essentially copy the base laravel auth functionality)
5. add some middleware to the constructor of the LoginController so only guests hit these routes (helps prevent an infinite loop)
6. override the unauthenticated
method on the app\Exceptions\Handler.php
class to the following (this will catch any unauthenticated users trying to navigate to the admin routes and redirect to the correct admin login route)
7. create a view to be returned with the login form
8. remove ->login()
from the Filament panel
9. add ->authGuard('admin')
to the filament panel config so only users authenticated as App\Models\Admin
can see the panelnow that I know my login system will work, I can be fairly confident that other login features (2fa, exception handling, rate limiting) will work when it comes time to implement filament in my actual application
Impersonation
Ok, now that I have my authentication system in place, the next thing I need to figure out is Impersonation. For those who don't know, impersonation allows users to authenticate as a different user, this is useful if as an admin you get a bug report from a user but you're not sure what they are referring to, this allows the admin to login as the user without knowing their password, and replicate the steps required to replicate the bug.
In the case of my application, I use the impersonation feature frequently in both production and development to easily switch between the different sections of the application. The use case I'm going to try replicate into the fresh laravel+filament installation is logging in as an
App\Models\Admin
and impersonating a App\Models\User
I found this plugin for Impersonation however, I'm not a huge fan of adding additional composer packages to my project, especially since I already have laravel-impersonate in my project, and will need to use it's implementation outside of Filament as well anyway. Let's see how the plugin works, and see if I can implement this simply myself without needing to use the plugin
Ok, so let's start by replicating the same impersonation setup as my application in the filament project.
First I'm going to need a User Resource with a table that I can add the Impersonate Action to.
php artisan make:filament-resource User --generate
I'm using the --generate
flag here to quickly create a form and table from my user's schema
Next let's install the laravel impersonate package
composer require lab404/laravel-impersonate
Now we can add the Action to the table:
And some routes to simulate where the impersonated user would be redirected to (also, a leave impersonation route so the admin user can get out of impersonating)
These routes also have an auth middleware on the as auth:user
so we can be sure that if you are on these routes you have to be authenticated as an App\Models\User
The User's Dashboard controller can be created next
And a simple view to test the basic functionality
Now that we have that in place, it would be nice to just have to redirect()->route('home');
and the application just knows which "home" you're referring to. To do this, I created a HomeController
and home
route as follows
Route::get('home', [Auth\HomeController::class, 'home'])->name('home');
Now no matter where we are in the application, redirecting to home will check which type of user is authenticated and redirect to the right place.
Let's test these changes...It works well, we're redirected to the user's home page, and logged in as them, with a nice banner so we know which user we are impersonating and a link to return back to the admin panel
And now a slight detour to chat with Adam about this Thread π
I love all of this, but I think it would be great as a blog series. The value of what you are doing might get lost in discord.
And it needs to be seen and read.
Could even be really cool as an rss feed.
Thanks! I'm just documenting it here as I go. I don't have a blog so figured this was better than nothing π
No doubt. Iβm totally on board. I just think it needs more exposure than on discord.
But please keep it going.
Thanks π I'm not sure the best way to share it, perhaps on twitter, but I don't have a large following there
Personally, I would set it up as a blog series, and leverage x / Twitter to promote new posts. Or even a subscription based newsletter in conjunction so people could sign up to receive notices when you post a new addition.
Great idea! Appreciate the feedback
I feel that if you took the time to set that up, the community would also share it, thus increasing your reach. I think thereβs value to what you are willing to share.
That makes sense π I'm going to keep going here in the mean time, just because I have some momentum, but I'll look into splitting it into a blog series later!
Do it.
Iβm not trying to stop you.
Themes, Fonts and Styling
Next let's look at styling Filament to resemble the rest of our application.
We have a dark, space themed website, with some custom fonts and colors, so let's incorporate them into our admin panel.
We can force the admin panel to always be in dark mode by adding the following config to our AdminPanelProvider:
->darkMode(true, true)
For colors, I think the combination of sky
and slate
will work well.
Next let's add our fonts. We have a custom font called "Muli" that we want to use for the admin panel. We can use the
->font()
method to set the font for the entire admin panel. Since we want to use a locally hosted font, we need to pass the LocalFontProvider::class
as the second parameter to the ->font()
method.
The font is a subtle change, but will make the site look more cohesive, especially when we add our logo next...
There are a couple of ways to add a logo in Filament. The simplest way is to just add a
brandName
which will change the text in the top left. While this might suit most applications use case, I have a custom logo that I'd like to use instead.->brandName('Space Coders')
To implement the custom logo, we can either pass a URL to the
brandLogo()
function or we can pass HTML view a closure. eg. ->brandLogo(fn () => view('filament.admin.logo'));
. I've opted for the second option since my logo is just a svg shield and text styled with tailwind.
logo.blade.php file contents:
Here's how it all looks together so far
the logo is not vertically aligned π
Thanks, I'll fix this soon
Updated logo alignment, thanks @dissto
I think we can really step this up a notch by adding a custom background. To do this we're going to need to extend the default theme's css with our own custom theme. Let's do that with
php artisan make:filament-theme
. Now we have a theme.css
file that we can use to override some styles.
First, let's get our custom fonts working, I've repeated this for all the font weights:
Next let's add some styles to the classes that filament provides us as hooks to override the default styling.
The .fi-body
seems like a good place to set a background image
This is looking pretty nice, however, I don't like the top bar being a solid color and the borders around the top bar look a bit inconsistent now as well. let's override that with a dark gray color with some transparency to let the the image come through, and apply some blur to the backdrop.
We had to override two classes, since the topbar and the sidebar have separate header classes. We also need to use
!
to make our classes more important than the default styles in the case where those styles are already being defined.
Here's how it all looks nowHere's the Users index page
The User/Edit page, however, might be hard to read with the background
We can fix this fairly easily by wrapping the form in a Section
Thats neither vertically nor horizontally aligned π
That bg is mighty nice though π
π€¦ββοΈ I think itβs good enough for now π
Thanks π