Skip to content

Commit

Permalink
Update 'Customizing the login page' recipe
Browse files Browse the repository at this point in the history
  • Loading branch information
lcharette committed Apr 28, 2024
1 parent 291ec71 commit abf7302
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 58 deletions.
140 changes: 82 additions & 58 deletions pages/20.recipes/04.custom-login-page/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,30 @@ taxonomy:
category: docs
---

[notice]This recipe assumes that you've already setup your own sprinkle and you're familiar with the basics of UserFrosting.[/notice]
[notice=note]This recipe assumes that you've already setup your own UserFrosting instance from the Skeleton template and you're familiar with the basics of UserFrosting.[/notice]

[notice=tip]A complete example of this guide can be found on GitHub : [https://github.com/userfrosting/recipe-custom-login](https://github.com/userfrosting/recipe-custom-login)[/notice]

This recipe will guide you in customizing the UserFrosting login screen. Specifically, we'll explain how to:
- Disable the registration
- Change the destination the user is taken to after a successful login
- Changing the visual style of the login page

If you haven't already, set up your site Sprinkle as per the instructions in ["Your First UserFrosting Site"](/sprinkles/first-site). For the purposes of this tutorial, we will call our Sprinkle `site` with `App\Site` as a base namespace..
If you haven't already, set up your site Sprinkle as per the installation instructions. For the purposes of this tutorial, we will use the same info as the Skeleton : call our Sprinkle recipe `MyApp` with `UserFrosting\App` as a base namespace.

[notice]This recipe was originally sponsored by [adm.ninja](https://adm.ninja). [Get in touch with the UserFrosting team](https://chat.userfrosting.com) if you want to sponsor a custom recipe for your organization![/notice]

## Disabling registration

For many reasons, you may want to disable the ability for someone to create a new account. Fortunately, UserFrosting provides an option inside the [configuration files](/configuration/config-files) to disable the registration feature. Since you should never modify code directly in the core UserFrosting codebase, the clean way to do this is to **override the default configuration in your own Sprinkle**.

To do this, first we'll need to create a `config/` directory inside your Sprinkle directory structure. Inside this directory, we'll create a PHP file named `default.php`.
To do this, first we'll need to create a `app/config/` directory inside your Sprinkle directory structure. Inside this directory, we'll create a PHP file named `default.php`.

[notice=tip]The name of the configuration file is important. The `default` config file will be automatically loaded when your Sprinkle is included by the system. See the [Environment Mode](/configuration/config-files#environment-modes) chapter if you want to edit a configuration value for another environment mode.[/notice]

Inside your newly created `config/default.php` file, you add any configuration options you want to overwrite or add. In this case, we want to set the `site.registration.enabled` option to `false`:
Inside your newly created config file, you add any configuration options you want to overwrite or add. In this case, we want to set the `site.registration.enabled` option to `false`:

`app/config/default.php`
```php
<?php
return [
Expand All @@ -48,8 +51,11 @@ See the [Configuration Files](/configuration/config-files) chapter for more info

## Changing the post-login destination

When a successful login occurs, by default, the user will be taken to the `/dashboard` page if it has access to this page, or `/settings` otherwise. To understand how this redirect works, when the user is logging in, the `UserRedirectedAfterLoginEvent` event will be dispatched. The Admin Sprinkle listen to this event, through the `UserRedirectedToSettings` and `UserRedirectedToDashboard` listeners :
When a successful login occurs, by default, the user will be taken to the `/dashboard` page if it has access to this page, or `/settings` otherwise.

To understand how this redirect works, when the user is logging in, the `UserRedirectedAfterLoginEvent` event will be dispatched. The **Admin Sprinkle** listen to this event, through the `UserRedirectedToSettings` and `UserRedirectedToDashboard` listeners :

`vendor/userfrosting/sprinkle-admin/app/src/Admin.php`
```php
/**
* N.B.: Last listeners will be executed first.
Expand All @@ -67,14 +73,15 @@ public function getEventListeners(): array

You'll notice two listeners are listening to the event. First `UserRedirectedToDashboard` will be executed and if the user has access to the Dashboard, the redirect will be set to `/dashboard`. If the user doesn't have access (via the authenticator checkAccess method), `UserRedirectedToSettings` will be called.

This process can be easily customized by adding our own event listener. Let's change the default behavior to redirect every user to the index page (`/` route) upon login.
This process can be easily customized by adding our own event listener. Let's change the default behavior to redirect every user to the about page (`/about` route) upon login.

First, create a class `src/Listener/UserRedirectedToIndex.php` in your Sprinkle with the following content:
First, create a `UserRedirectedToAbout` class in your Sprinkle with the following content:

`app/src/Listener/UserRedirectedToAbout.php`
```php
<?php

namespace App\Site\Listener;
namespace UserFrosting\App\Listener;

use Psr\EventDispatcher\StoppableEventInterface;
use UserFrosting\Sprinkle\Core\Event\Contract\RedirectingEventInterface;
Expand All @@ -83,7 +90,7 @@ use UserFrosting\Sprinkle\Core\Util\RouteParserInterface;
/**
* Set redirect to index.
*/
class UserRedirectedToIndex
class UserRedirectedToAbout
{
public function __construct(
protected RouteParserInterface $routeParser,
Expand All @@ -95,36 +102,44 @@ class UserRedirectedToIndex
*/
public function __invoke($event): void
{
$path = $this->routeParser->urlFor('index');
$path = $this->routeParser->urlFor('about');
$event->setRedirect($path);
$event->isPropagationStopped();
}
}
```

[notice=note]Note that we use Slim's route parser `urlFor` method to get the route definition from it's name. This is the same as hardcoding `'/'`. Check out [Slim's documentation](https://www.slimframework.com/docs/objects/router.html#route-names) for more info on named routes.[/notice]
[notice=note]Note that we use Slim's route parser `urlFor` method to get the route definition from it's name. This is the same as hardcoding `'/about'`. Check out [Slim's documentation](https://www.slimframework.com/docs/objects/router.html#route-names) for more info on named routes.[/notice]

The last step is to register the new listener in your Sprinkle Recipe. The recipe itself will also need to implement the `UserFrosting\Event\EventListenerRecipe`.
The last step is to register the new listener in your Sprinkle Recipe. The recipe itself will also need to implement the `UserFrosting\Event\EventListenerRecipe` interface.

`app/src/MyApp.php`
```php
namespace App;

use App\Site\Listener\UserRedirectedToIndex; // <-- Add here !
use UserFrosting\Event\EventListenerRecipe; // <-- Add here !
use UserFrosting\Sprinkle\Account\Event\UserRedirectedAfterLoginEvent; // <-- Add here !
namespace UserFrosting\App;

use UserFrosting\App\Bakery\HelloCommand;
use UserFrosting\App\Listener\UserRedirectedToAbout; // <-- Add this
use UserFrosting\Event\EventListenerRecipe; // <-- Add this
use UserFrosting\Sprinkle\Account\Account;
use UserFrosting\Sprinkle\Account\Event\UserRedirectedAfterLoginEvent; // <-- Add this
use UserFrosting\Sprinkle\Admin\Admin;
use UserFrosting\Sprinkle\BakeryRecipe;
use UserFrosting\Sprinkle\Core\Core;
use UserFrosting\Sprinkle\SprinkleRecipe;
use UserFrosting\Theme\AdminLTE\AdminLTE;

class Site implements
SprinkleRecipe,
EventListenerRecipe, // <-- Add here !
class MyApp implements
SprinkleRecipe,
BakeryRecipe
EventListenerRecipe, // <-- Add this
{
// ...

public function getEventListeners(): array
{
return [
UserRedirectedAfterLoginEvent::class => [
UserRedirectedToIndex::class,
UserRedirectedToAbout::class,
],
];
}
Expand All @@ -143,51 +158,72 @@ Customizing the visual style of the login page is similar to any other component

### Customizing the template

Let's start by overriding the base template for the login page. First, copy the base template to your sprinkle. You'll find the base template in `app/sprinkles/account/templates/pages/sign-in.html.twig`. Once copied over to your sprinkle, any changes made to your copy will override the base version, as long as it follows the same folder structure.

For this example, let's change the site title position:
Now we will modify the login page to show the site logo. The base base template for this page is defined in `vendor/userfrosting/theme-adminlte/app/templates/pages/sign-in.html.twig`. However, we can't directly edit this file. We could copy the whole file from the AdminLTE theme, however what happens if this file changes in a future version? Our custom version would need to be updated as well. To minimize this, we will instead use the [extending templates](https://learn.userfrosting.com/recipes/extending-template) feature seen on the previous page. Create a new template file with the same name (`sign-in.html.twig`) and at the same location as the original one (`app/templates/pages`) and copy the next block.

`app/templates/pages/sign-in.html.twig`
```html
{% extends '@adminlte-theme/pages/sign-in.html.twig' %}

{% block loginLogo %}{% endblock %}

{% block loginBox %}
<div class="login-box">
<div class="login-box-body login-form">
<div class="login-logo">
<a href="{{site.uri.public}}">
<img src="{{assets.url('assets://userfrosting/images/cupcake.png')}}">
<img src="{{ asset('assets/images/cupcake.svg') }}" style="width: 200px">
<p>{{site.title}}</p>
</a>
</div>
<!-- /.login-logo -->

<div class="form-alerts" id="alerts-page"></div>

<form action="{{site.uri.public}}/account/login" id="sign-in" method="post">
...
</form>

<a href="{{site.uri.public}}/account/forgot-password">{{translate('PASSWORD.FORGET')}}</a><br>
<a href="{{site.uri.public}}/account/resend-verification">{{translate('ACCOUNT.VERIFICATION.RESEND')}}</a><br>
{% if site.registration.enabled %}
<a href="{{site.uri.public}}/account/register">{{translate('REGISTER')}}</a>
{% endif %}
{% block loginForm %}
{{ parent() }}
{% endblock %}

{% block loginLinks %}
{{ parent() }}
{% endblock %}
</div>
<!-- /.login-box-body -->
</div>
{% endblock %}
```

Did you notice how the `login-logo` div is now inside the `login-box-body` and the image, from the *core* sprinkle, was added? Once you refresh the page, you should see the result:
A couple of elements to point out here :
1. We start by using the `extend` tag with the reference to `@adminlte-theme` template. This tells Twig to use the original template as base.
2. We add the `loginLogo`, but leave it empty to remove the default logo/title.
3. We define a custom `loginBox` block with our modified code. The `login-logo` div is now inside the `login-box-body` and the image is added.
4. Since we still want to use both `loginForm` and `loginLinks` blocks, but don't want to change their content, we include them and use `{{ parent() }}` to render the content from the `@adminlte-theme` version

![Custom login template](/images/custom-login.png)
The last missing piece is the image we used isn't loaded in your app right now. This image is located in the AdminLTE theme sprinkle and must be copied to the `public/`. Fortunately, AdminLTE sprinkle provides an helper module to do this with Webpack. Simply add the following line to your `app/assets/app.js` and run Webpack.

`app/assets/app.js`
```
require('@userfrosting/theme-adminlte/app/assets/cupcake');
```

[notice=tip]You can also use the blocks definition to partially edit a template. See the [Extending Templates and Menus](/recipes/extending-template) recipe for more information.[/notice]
Run :
```
$ php bakery assets:build
```

Once you refresh the page, you should see the result:

![Custom login template](/images/custom-login.png)

### Customizing the CSS

Customizing the CSS is similar to overriding the template, except that it involves registering a new asset file in the asset-bundle definitions. Since UserFrosting uses the [AdminLTE](https://adminlte.io) theme, the default login page style comes directly from AdminLTE and can't be overridden by simply replacing a CSS file. We'll need to create our own custom CSS file and add it to the assets bundle to override the default CSS rules.
Customizing the CSS is different than overriding the template. Our change will be small, so we won't be replacing a full css file, but add a new, smaller one. Two steps are required to achieve this :

1. Create a new css file
2. Register a new webpack entry

First, let's create a new `assets/css/login-page.css` file and add the following code to that file. This will invert the colors of the background and login box on the login page. Feel free to make whatever styling changes you want on that page here.
First, let's create a new `app/assets/css/sign-in.css` file and add the following code to that file. This will invert the colors of the background and login box on the login page. Feel free to make whatever styling changes you want on that page here.

**app/assets/css/login-page.css**
**app/assets/css/sign-in.css**
```css
.login-page {
background-color: #ffffff;
Expand All @@ -198,42 +234,30 @@ First, let's create a new `assets/css/login-page.css` file and add the following
}
```

Second, we need to create the page ES module, to add our custom CSS file. Note the page Javascript, from AdminLTE theme, need to be defined here:

**app/assets/login-page.js**
```js
import 'theme-adminlte/app/assets/userfrosting/js/pages/sign-in';
import './css/login-page.css';
```

[notice=note]It could also be possible to define a separate entry, and load both the default entry and our custom entry on the same page[/notice]

Next, we need to replace the `page.sign-in` webpack entry. In your Sprinkle's `/webpack.entries.js`, add this entry:
Next, we need to create a webpack entry for the new css file. We'll assign it to the `css.sign-in` entry. In your Sprinkle's `/webpack.entries.js`, add this entry:

**webpack.entries.js**
```js
module.exports = {
// ...
'page.sign-in': './app/assets/sign-in',
'css.sign-in': './app/assets/css/sign-in.css',
// ...
}
```

Next, the login page doesn't have a page CSS by default. We need to fix this by extending the default page template:
Next, we add the entry to the login page. At the bottom of the file, outside any of the block, add the `stylesheets_page` block:

**app/templates/pages/sign-in.html.twig**
```html
{% extends "@adminlte-theme/pages/sign-in.html.twig" %}

{% block stylesheets_page %}
{{ encore_entry_link_tags('page.sign-in') }}
{{ encore_entry_link_tags('css.sign-in') }}
{% endblock %}
```

Last, we need to rebuild the assets, by running the bakery bake command :
Last, we need to rebuild the assets, by running the bakery command :

```bash
php bakery bake
php bakery assets:build
```

Your new CSS file should be loaded when you refresh the page and you should see the result:
Expand Down
Binary file modified pages/images/custom-login.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified pages/images/custom-login2.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit abf7302

Please sign in to comment.