F
Filamentβ€’11mo ago
Josh

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
Josh
JoshOPβ€’11mo ago
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:
<?php

return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
*/

'defaults' => [
'guard' => 'user',
'passwords' => 'users',
],

/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
*/

'guards' => [
'user' => [
'driver' => 'session',
'provider' => 'users',
],

'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
],

/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
*/

'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],

'admins' => [
'driver' => 'eloquent',
'model' => App\Models\Admin::class,
],
],

...

];
<?php

return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
*/

'defaults' => [
'guard' => 'user',
'passwords' => 'users',
],

/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
*/

'guards' => [
'user' => [
'driver' => 'session',
'provider' => 'users',
],

'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
],

/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
*/

'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],

'admins' => [
'driver' => 'eloquent',
'model' => App\Models\Admin::class,
],
],

...

];
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 first
Josh
JoshOPβ€’11mo ago
Looking 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
No description
Josh
JoshOPβ€’11mo ago
Yes, it seems that my assumption was correct, Filament::auth() is using Laravel's StatefulGuard implementation under the hood
No description
Josh
JoshOPβ€’11mo ago
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
No description
No description
Josh
JoshOPβ€’11mo ago
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.
Josh
JoshOPβ€’11mo ago
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
Josh
JoshOPβ€’11mo ago
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
No description
Josh
JoshOPβ€’11mo ago
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.
Route::get('admin/login', [LoginController::class, 'show']);
Route::post('admin/login', [LoginController::class, 'store']);
Route::get('admin/login', [LoginController::class, 'show']);
Route::post('admin/login', [LoginController::class, 'store']);
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)
public function __construct()
{
$this->middleware('guest:admin')->except('logout');
}
public function __construct()
{
$this->middleware('guest:admin')->except('logout');
}
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)
// redirect to admin login page if user is not authenticated
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error'=>'Unauthenticated.'], 401);
}

if ($request->is('admin') || $request->is('admin/*')) {
return redirect()->guest('admin/login');
}

return redirect()->guest(route('login'));
}
// redirect to admin login page if user is not authenticated
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error'=>'Unauthenticated.'], 401);
}

if ($request->is('admin') || $request->is('admin/*')) {
return redirect()->guest('admin/login');
}

return redirect()->guest(route('login'));
}
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 panel
No description
No description
Josh
JoshOPβ€’10mo ago
now 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:
Action::make('impersonate')
->action(function(User $user) {
Auth::user()->impersonate($user, 'user'); // the second argument specifies to use the user guard

return redirect()->route('home');
}),
Action::make('impersonate')
->action(function(User $user) {
Auth::user()->impersonate($user, 'user'); // the second argument specifies to use the user guard

return redirect()->route('home');
}),
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)
Route::prefix('portal')->middleware('auth:user')->name('portal.')->group(function () {
Route::get('home', [User\DashboardController::class, 'home'])->name('home');
Route::get('leaveImpersonation', [User\DashboardController::class, 'leaveImpersonation'])->name('impersonate.leave');
});
Route::prefix('portal')->middleware('auth:user')->name('portal.')->group(function () {
Route::get('home', [User\DashboardController::class, 'home'])->name('home');
Route::get('leaveImpersonation', [User\DashboardController::class, 'leaveImpersonation'])->name('impersonate.leave');
});
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
<?php

namespace App\Http\Controllers\User;

use App\Http\Controllers\Controller;

class DashboardController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth:user');
}

public function home()
{
return view('portal.home');
}

public function leaveImpersonation()
{
$manager = app('impersonate');
$manager->leave();

return redirect('home');
}
}
<?php

namespace App\Http\Controllers\User;

use App\Http\Controllers\Controller;

class DashboardController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth:user');
}

public function home()
{
return view('portal.home');
}

public function leaveImpersonation()
{
$manager = app('impersonate');
$manager->leave();

return redirect('home');
}
}
And a simple view to test the basic functionality
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta name="robots" content="noindex,nofollow">
<title>Portal</title>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="csrf-token" content="{{ csrf_token() }}">
</head>
<body style="background-color: rgb(30 27 75); height: 100vh;">
@impersonating
<div style="background-color: #434190; display: flex; justify-content: center;">
<div style="border-radius: 0.375rem; display: flex; justify-content: flex-start; width: 90%; padding: 12px; color: white; font-family: 'Muli', sans-serif;">
<div>
You are currently impersonating {{ Auth::user()->name }}
</div>
<div>
<a style="color: #79CDDA; margin-left: 40px;" href="{{ route('portal.impersonate.leave') }}">Leave impersonation</a>
</div>
</div>
</div>
@endImpersonating
<p style="color: white; font-family: 'Muli', sans-serif;">Portal Home</p>
<form action="{{ route('logout') }}" method="POST">
{{ csrf_field() }}
<button type="submit">Logout</button>
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta name="robots" content="noindex,nofollow">
<title>Portal</title>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="csrf-token" content="{{ csrf_token() }}">
</head>
<body style="background-color: rgb(30 27 75); height: 100vh;">
@impersonating
<div style="background-color: #434190; display: flex; justify-content: center;">
<div style="border-radius: 0.375rem; display: flex; justify-content: flex-start; width: 90%; padding: 12px; color: white; font-family: 'Muli', sans-serif;">
<div>
You are currently impersonating {{ Auth::user()->name }}
</div>
<div>
<a style="color: #79CDDA; margin-left: 40px;" href="{{ route('portal.impersonate.leave') }}">Leave impersonation</a>
</div>
</div>
</div>
@endImpersonating
<p style="color: white; font-family: 'Muli', sans-serif;">Portal Home</p>
<form action="{{ route('logout') }}" method="POST">
{{ csrf_field() }}
<button type="submit">Logout</button>
</form>
</body>
</html>
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');
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;

class HomeController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
}

/**
* Route the user based off their admin status.
*
* @return \Illuminate\Http\Response
*/
public function home()
{
if (Auth::guard('user')->check()) {
return redirect()->route('portal.home');
}

if (Auth::guard('admin')->check()) {
return redirect()->route('filament.admin.pages.dashboard');
}

return redirect()->route('home');
}
}
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;

class HomeController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
}

/**
* Route the user based off their admin status.
*
* @return \Illuminate\Http\Response
*/
public function home()
{
if (Auth::guard('user')->check()) {
return redirect()->route('portal.home');
}

if (Auth::guard('admin')->check()) {
return redirect()->route('filament.admin.pages.dashboard');
}

return redirect()->route('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...
Josh
JoshOPβ€’10mo ago
No description
Josh
JoshOPβ€’10mo ago
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 😁
awcodes
awcodesβ€’10mo ago
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.
Josh
JoshOPβ€’10mo ago
Thanks! I'm just documenting it here as I go. I don't have a blog so figured this was better than nothing πŸ˜…
awcodes
awcodesβ€’10mo ago
No doubt. I’m totally on board. I just think it needs more exposure than on discord. But please keep it going.
Josh
JoshOPβ€’10mo ago
Thanks πŸ™‚ I'm not sure the best way to share it, perhaps on twitter, but I don't have a large following there
awcodes
awcodesβ€’10mo ago
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.
Josh
JoshOPβ€’10mo ago
Great idea! Appreciate the feedback
awcodes
awcodesβ€’10mo ago
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.
Josh
JoshOPβ€’10mo ago
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!
awcodes
awcodesβ€’10mo ago
Do it. I’m not trying to stop you.
Josh
JoshOPβ€’10mo ago
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.
->colors([
'primary' => Color::Sky,
'gray' => Color::Slate,
])
->colors([
'primary' => Color::Sky,
'gray' => Color::Slate,
])
Josh
JoshOPβ€’10mo ago
No description
Josh
JoshOPβ€’10mo ago
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.
->font('Muli', provider: LocalFontProvider::class)
->font('Muli', provider: LocalFontProvider::class)
Josh
JoshOPβ€’10mo ago
No description
Josh
JoshOPβ€’10mo ago
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.
Josh
JoshOPβ€’10mo ago
->brandName('Space Coders')
No description
Josh
JoshOPβ€’10mo ago
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:
<span class="flex items-center">
<svg width="24" height="31" viewBox="0 0 236 307" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 143.957c0 51.803 36.35 118.874 110.703 161.407 1.653 1.091 3.856 1.636 6.059 1.636 0-1.091-8.262-47.986-8.262-145.593 0-28.901.551-55.075 1.653-76.887H0v59.437z" fill="#20A8F2"/><path d="M115.66 0c-3.304 0-6.058 1.09-8.812 2.726C94.18 13.087 66.092 33.808 13.769 36.535 6.06 37.08 0 43.623 0 51.258v26.719h110.153C112.356 29.99 115.66 1.636 115.66 0z" fill="#5B5859"/><path d="M108.5 160.861c0 97.607 8.262 144.502 8.262 145.593H118.414c1.102 0 2.203 0 3.305-.545h.55c1.102-.546 2.203-.546 3.305-1.091 73.802-42.533 109.602-110.149 109.602-161.406V83.975H110.153s-1.102 22.357-1.102 39.261c-.551 15.813-.551 37.625-.551 37.625z" fill="#2477B7"/><path d="M115.66 0c1.653 0 3.305 0 4.406.545 1.653.546 2.754 1.09 4.406 2.181 13.219 10.361 44.061 30.537 95.833 33.263 7.711.546 13.769 6.544 13.769 14.178v27.265H110.153s0-10.361 2.203-38.17C114.559 11.451 115.66 0 115.66 0z" fill="#413E3F"/>
</svg>
<span class="mt-1 ml-2 text-white font-light text-xl uppercase tracking-wider subpixel-antialiased">Space</span>
<span class="mt-1 text-sky-500 font-black text-xl uppercase tracking-wider subpixel-antialiased">Coders</span>
</span>
<span class="flex items-center">
<svg width="24" height="31" viewBox="0 0 236 307" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 143.957c0 51.803 36.35 118.874 110.703 161.407 1.653 1.091 3.856 1.636 6.059 1.636 0-1.091-8.262-47.986-8.262-145.593 0-28.901.551-55.075 1.653-76.887H0v59.437z" fill="#20A8F2"/><path d="M115.66 0c-3.304 0-6.058 1.09-8.812 2.726C94.18 13.087 66.092 33.808 13.769 36.535 6.06 37.08 0 43.623 0 51.258v26.719h110.153C112.356 29.99 115.66 1.636 115.66 0z" fill="#5B5859"/><path d="M108.5 160.861c0 97.607 8.262 144.502 8.262 145.593H118.414c1.102 0 2.203 0 3.305-.545h.55c1.102-.546 2.203-.546 3.305-1.091 73.802-42.533 109.602-110.149 109.602-161.406V83.975H110.153s-1.102 22.357-1.102 39.261c-.551 15.813-.551 37.625-.551 37.625z" fill="#2477B7"/><path d="M115.66 0c1.653 0 3.305 0 4.406.545 1.653.546 2.754 1.09 4.406 2.181 13.219 10.361 44.061 30.537 95.833 33.263 7.711.546 13.769 6.544 13.769 14.178v27.265H110.153s0-10.361 2.203-38.17C114.559 11.451 115.66 0 115.66 0z" fill="#413E3F"/>
</svg>
<span class="mt-1 ml-2 text-white font-light text-xl uppercase tracking-wider subpixel-antialiased">Space</span>
<span class="mt-1 text-sky-500 font-black text-xl uppercase tracking-wider subpixel-antialiased">Coders</span>
</span>
Josh
JoshOPβ€’10mo ago
Here's how it all looks together so far
No description
dissto
disstoβ€’10mo ago
the logo is not vertically aligned 😊
Josh
JoshOPβ€’10mo ago
Thanks, I'll fix this soon
Josh
JoshOPβ€’10mo ago
Updated logo alignment, thanks @dissto
No description
Josh
JoshOPβ€’10mo ago
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:
@font-face {
font-family: 'Muli';
src: url('/fonts/muli/Muli-Regular.ttf') format('truetype');
font-weight: 400;
}
@font-face {
font-family: 'Muli';
src: url('/fonts/muli/Muli-Regular.ttf') format('truetype');
font-weight: 400;
}
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
.fi-body {
@apply bg-center bg-cover relative -z-10;
background-image: url('/admin/assets/images/bg.png');
}
.fi-body {
@apply bg-center bg-cover relative -z-10;
background-image: url('/admin/assets/images/bg.png');
}
Josh
JoshOPβ€’10mo ago
No description
Josh
JoshOPβ€’10mo ago
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.
.fi-topbar nav {
@apply !bg-slate-950/50 !ring-transparent !shadow-none backdrop-blur;
}

.fi-sidebar-header {
@apply !bg-slate-950/50 !ring-transparent !shadow-none backdrop-blur;
}
.fi-topbar nav {
@apply !bg-slate-950/50 !ring-transparent !shadow-none backdrop-blur;
}

.fi-sidebar-header {
@apply !bg-slate-950/50 !ring-transparent !shadow-none backdrop-blur;
}
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 now
Josh
JoshOPβ€’10mo ago
No description
Josh
JoshOPβ€’10mo ago
Here's the Users index page
Josh
JoshOPβ€’10mo ago
No description
Josh
JoshOPβ€’10mo ago
The User/Edit page, however, might be hard to read with the background
Josh
JoshOPβ€’10mo ago
No description
Josh
JoshOPβ€’10mo ago
We can fix this fairly easily by wrapping the form in a Section
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Section::make()
->columns(2)
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
Forms\Components\TextInput::make('email')
->email()
->required()
->maxLength(255),
Forms\Components\DateTimePicker::make('email_verified_at'),
Forms\Components\TextInput::make('password')
->password()
->required()
->maxLength(255),
]),
]);
}
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Section::make()
->columns(2)
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
Forms\Components\TextInput::make('email')
->email()
->required()
->maxLength(255),
Forms\Components\DateTimePicker::make('email_verified_at'),
Forms\Components\TextInput::make('password')
->password()
->required()
->maxLength(255),
]),
]);
}
Josh
JoshOPβ€’10mo ago
No description
dissto
disstoβ€’10mo ago
Thats neither vertically nor horizontally aligned 😊 That bg is mighty nice though πŸ‘Œ
Josh
JoshOPβ€’10mo ago
πŸ€¦β€β™‚οΈ I think it’s good enough for now πŸ˜… Thanks 😊
Want results from more Discord servers?
Add your server