Open Closed

Angular : Modification to the logon screen #154


User avatar
0
ian@order2invoice.com created

Can I get some direction on acheiving the following two issues related to the logon screen

  1. Remove teh tenant selection from the logon screen and provide it only to admin users after logon. This could be on another page prior to showing the app or from a button in the app, maybe on the top toolbar.
  2. Modify the layout of the logon page to display items other than the logon card, for say marketing information.

Thank you.


16 Answer(s)
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Are you using razor page or angular UI?

  • User Avatar
    0
    ian@order2invoice.com created

    Angular

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    See https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement#how-to-replace-a-layout. You should replace AccountLayoutComponent.

  • User Avatar
    0
    ian@order2invoice.com created

    Yes I have seen that. Two issues

    1. That solution requires us to recreate what is already there which we do not know. (i.e. what apis are called to accomplish login and language selection etc?). I suppose we can hide what we need using css but this is not desirable.
    2. How do we provide tenant login without the logon page?

    FYI. Just a thought. I still do not understand why these layouts are not simple provided so we can just modify them as needed. Is it intended to push sales of higher licenses?

  • User Avatar
    0
    Mehmet created

    Hi,

    I think you can achieve what you want by replacing AccountComponent.

    Run the following command to create a component:

    yarn ng generate component account --entryComponent
    

    Then open account.component.ts in app/account folder and replace the content with below:

    import {
      ApplicationConfiguration,
      Config,
      ConfigState,
      getAbpRoutes,
      SessionState,
      SetLanguage,
    } from '@abp/ng.core';
    import { Component } from '@angular/core';
    import { Router } from '@angular/router';
    import { Select, Store } from '@ngxs/store';
    import { eAccountComponents } from '@volo/abp.ng.account';
    import { Observable } from 'rxjs';
    import { map } from 'rxjs/operators';
    import snq from 'snq';
    
    @Component({
      selector: 'abp-account',
      templateUrl: './account.component.html',
    })
    export class AccountComponent {
      @Select(ConfigState.getDeep('multiTenancy.isEnabled'))
      isMultiTenancyEnabled$: Observable<boolean>;
    
      @Select(ConfigState.getDeep('localization.languages'))
      languages$: Observable<ApplicationConfiguration.Language[]>;
    
      tenantBoxKey = eAccountComponents.TenantBox;
    
      get defaultLanguage$(): Observable<{ displayName: string; flagIcon: string }> {
        return this.languages$.pipe(
          map(languages => {
            const lang: Partial<ApplicationConfiguration.Language> = snq(
              () => languages.find(l => l.cultureName === this.selectedLangCulture),
              {} as any,
            );
            return {
              displayName: lang.displayName || '',
              flagIcon: lang.flagIcon,
            };
          }),
        );
      }
    
      get dropdownLanguages$(): Observable<ApplicationConfiguration.Language[]> {
        return this.languages$.pipe(
          map(
            languages =>
              snq(() => languages.filter(lang => lang.cultureName !== this.selectedLangCulture)),
            [],
          ),
        );
      }
    
      get selectedLangCulture(): string {
        return this.store.selectSnapshot(SessionState.getLanguage);
      }
    
      get appInfo(): Config.Application {
        return this.store.selectSnapshot(ConfigState.getApplicationInfo);
      }
    
      get pageLabel(): string {
        return getAbpRoutes()
          .find(route => route.path === 'account')
          .children.reduce((acc, val) => {
            if (this.router.url.includes(val.path)) {
              return val.name;
            }
            return acc;
          }, '');
      }
    
      constructor(private store: Store, private router: Router) {}
    
      onChangeLang(cultureName: string) {
        this.store.dispatch(new SetLanguage(cultureName));
      }
    }
    

    Open account.component.html and replace content with the following:

    <div class="row">
      <div class="col col-md-6 col-lg-4 offset-md-3 offset-lg-4">
        <div class="account-brand p-4 text-center mb-1">
          <a #navbarBrand class="navbar-brand" routerLink="/" alt="Logo"></a>
        </div>
        <div class="card">
          <div class="card-header">
            <h2 class="card-title d-inline-block">{{ pageLabel | abpLocalization }}</h2>
            <nav class="navbar navbar-expand p-0 pt-1 float-right">
              <ul class="navbar-nav ml-auto toolbar-nav">
                <li class="nav-item">
                  <div class="dropdown" ngbDropdown>
                    <a
                      *ngIf="defaultLanguage$ | async as defaultLang"
                      class="pointer"
                      role="button"
                      id="dropdownMenuLink"
                      ngbDropdownToggle
                      [class.dropdown-toggle]="false"
                    >
                      <span
                        class="flag-icon flag-icon-squared flag-icon-{{ defaultLang.flagIcon }}"
                      ></span>
                    </a>
                    <div
                      ngbDropdownMenu
                      class="dropdown-menu dropdown-menu-right"
                      *ngIf="(dropdownLanguages$ | async).length > 0"
                    >
                      <a
                        *ngFor="let lang of dropdownLanguages$ | async"
                        class="dropdown-item pointer"
                        (click)="onChangeLang(lang.cultureName)"
                      >
                        <span
                          class="flag-icon flag-icon-{{ lang.flagIcon }} flag-icon-squared mr-2"
                        ></span>
                        {{ lang?.displayName }}</a
                      >
                    </div>
                  </div>
                </li>
              </ul>
            </nav>
          </div>
          <div class="card-body">
            <router-outlet></router-outlet>
          </div>
        </div>
    
        <div class="p-3 text-center text-muted">
          <div class="copyright">
            <span>© {{ appInfo.name }}</span
            ><br />
          </div>
        </div>
      </div>
    </div>
    

    Now, you should replace the AccountComponent with your own AccountComponent.

    Open app.component.ts;

    • Inject the Store
    • Dispatch the AddReplaceableComponent action.
    import { ..., AddReplaceableComponent } from '@abp/ng.core'; // import AddReplaceableComponent
    import { Store } from '@ngxs/store'; // import Store
    import { AccountComponent } from './account/account.component'; // import your AccountComponent
    import { eAccountComponents } from '@volo/abp.ng.account'; // import eAccountComponents
    // ...
    export class AppComponent implements OnInit {
      constructor(..., private store: Store) {} // injected store
    
      ngOnInit() {
    //...
    
        this.store.dispatch(
          new AddReplaceableComponent({ component: AccountComponent, key: eAccountComponents.Account }),
        );
      }
    }
    
  • User Avatar
    0
    ian@order2invoice.com created

    Almost there. So finally how do I make the starter templete open up directly with the login screen rather that the public page with a login button? I can find the path in app-routing.module.ts?

  • User Avatar
    0
    alper created
    Support Team Director

    @ian@order2invoice.com showing login page everytime you open the website is weird. Do you want to show login page even the user is authenticated?

  • User Avatar
    0
    Mehmet created

    Hi,

    You can protect your home page with the AuthGuard that redirects the users to the login page when the user is not authenticated. Replace the home page route in app-routing.module.ts as shown below:

    // AuthGuard can be imported from @abp/ng.core
    
    {
    path: '',
    loadChildren: () => import('./home/home.module').then(m => m.HomeModule),
    canActivate: [AuthGuard], // added this line
    data: {
      routes: {
        name: '::Menu:Home',
        iconClass: 'fa fa-home',
      } as ABP.Route,
    },
    }
    
  • User Avatar
    0
    ian@order2invoice.com created

    Hi Mehmet,

    The above works thanks. Thanks.

    However we need to provide our own Forget Password Page (we need to user Mailgun for emails which we can't find a way of doing within the existing framework), and Registration Page (we register a company, not a user). The links to both of these are hidden in the card-body as follows

    <div class="card-body"> <router-outlet></router-outlet> </div>

    Is there a way of modifying these to links to take user to our own pages?

  • User Avatar
    0
    Mehmet created

    Hi Ian,

    I created two GitHub gists to explain how to replace the RegisterComponent and the ForgotPasswordComponent.

  • User Avatar
    0
    ian@order2invoice.com created

    Thanks Mehmet,

    Almost there.

    1. Registration: If I want to process further information, like tenant name to create a new tenant for the user do I need to do another call to the server in the finalise clause? Or should I create a new endpoint to emulate the register call. If the latter, What is happening in this call. Is it simply creating a user, or is there more I need to do, or can to create an account service like in the easyCrm example and call registration that way.
     this.accountService.register(newUser)
    
    1. Forgotten User: The logic and emails for Forgotten User is together in this.accountService.sendPasswordResetCode. If I am using my own emailer I need to replace this with my own endpoint. What do I need to send. In .Net Core Identity I would do something like this but I don't know what to do is this case because I can't see the code.
             ...
             var user = await _userManager.FindByEmailAsync(Input.Email);
             var code = await _userManager.GeneratePasswordResetTokenAsync(user);
             var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme);
             ...
    
  • User Avatar
    0
    Mehmet created
    1. You can pass another fields to register method like below:
     this.accountService.register({...newUser, foo: 1, bar: 2} as any);
    

    If you create a new endpoint in backend, you should create your own service.

  • User Avatar
    0
    arifharsono created

    Hi @Mehmet

    I need azure AD integration in Angular Project, I tried in MVC project and it's working.

    How to modified module Account in angular Project ?

  • User Avatar
    0
    ian@order2invoice.com created

    ut where is the register function?

  • User Avatar
    0
    alper created
    Support Team Director

    Forgotten User: The logic and emails for Forgotten User is together in this.accountService.sendPasswordResetCode. If I am using my own emailer I need to replace this with my own endpoint. What do I need to send. In .Net Core Identity I would do something like this but I don't know what to do is this case because I can't see the code.

    to replace the existing email sender service; https://support.abp.io/QA/Questions/260/How-to-replace-the-existing-email-sender-with-my-own#answer-cc999500-981c-25df-e932-39f5f3e7f71b

  • User Avatar
    0
    Mehmet created

    Hi @arifharsono

    Can you create a new ticket and describe it in detail?

Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11