File

libs/common/frame/src/frame-parts/actions/actions.component.ts

Implements

OnInit

Metadata

Index

Properties
Methods
HostBindings
Accessors

HostBindings

class.expert-layout
Type : boolean
class.retail-layout
Type : boolean

Methods

cancel
cancel()
Returns : void
navigateBack
navigateBack()
Returns : void
navigateNext
navigateNext()
Returns : void
saveOffer
saveOffer()
Returns : void
triggerCustomAction
triggerCustomAction(customAction: CustomAction)
Parameters :
Name Type Optional
customAction CustomAction No
Returns : void

Properties

Readonly defaultBackLabel
Default value : $localize`:@@page-action.back-button-label:Back`
Readonly defaultCancelLabel
Default value : $localize`:@@page-action.cancel-button-label:Cancel`
Readonly defaultNextLabel
Default value : $localize`:@@page-action.next-button-label:Next`
pageActionStatus
Type : PageActionStatus | undefined

Accessors

isExpert
getisExpert()
isRetail
getisRetail()
nextButtonLabel
getnextButtonLabel()
backButtonLabel
getbackButtonLabel()
cancelButtonLabel
getcancelButtonLabel()
pageActionConfig
getpageActionConfig()
pageId$
getpageId$()
import {
  BackLinkAdapterService,
  BackLinkConfigElement,
  BackLinkUtilsService
} from '@allianz/taly-common/web-components';
import {
  CHANNEL,
  CHANNEL_TOKEN,
  CustomAction,
  PageActionConfig,
  TalyBusinessEventService,
  TalyPageDataService
} from '@allianz/taly-core';
import { Component, DestroyRef, HostBinding, inject, OnInit } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { LocalizeFn } from '@angular/localize/init';
import { TalyFrameNavigationService } from '../../services/taly-frame-navigation-service';
import { PageActionStatus } from './model';

declare let $localize: LocalizeFn;
@Component({
  selector: 'frame-actions',
  templateUrl: './actions.component.html',
  styleUrls: ['./actions.component.scss'],
  standalone: false
})
export class ActionsComponent implements OnInit {
  private backLinkData: BackLinkConfigElement | undefined;
  pageActionStatus: PageActionStatus | undefined;

  private talyPageDataService = inject(TalyPageDataService);
  private backLinkUtilsService = inject(BackLinkUtilsService);
  private businessEventService = inject(TalyBusinessEventService);
  private frameNavigationService = inject(TalyFrameNavigationService, { optional: true });
  private backLinkAdapterService = inject(BackLinkAdapterService, { optional: true });
  private _channel = inject(CHANNEL_TOKEN);
  private destroyRef = inject(DestroyRef);

  readonly defaultNextLabel = $localize`:@@page-action.next-button-label:Next`;
  readonly defaultBackLabel = $localize`:@@page-action.back-button-label:Back`;
  readonly defaultCancelLabel = $localize`:@@page-action.cancel-button-label:Cancel`;

  @HostBinding('class.expert-layout')
  get isExpert() {
    return this._channel === CHANNEL.EXPERT;
  }

  @HostBinding('class.retail-layout')
  get isRetail() {
    return this._channel === CHANNEL.RETAIL;
  }

  ngOnInit() {
    this.frameNavigationService?.pageActionStatus$
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe((status) => {
        this.pageActionStatus = status;
        this.backLinkData = this.getBackLinkData();
      });
  }

  private getBackLinkData(): BackLinkConfigElement | undefined {
    const backLinkFeatureActiveForActions =
      this.backLinkUtilsService.isBackLinkFeatureActiveForActions(
        Boolean(this.pageActionStatus?.isLastPage)
      );

    return backLinkFeatureActiveForActions ? this.backLinkAdapterService?.backLinkData : undefined;
  }

  get nextButtonLabel() {
    if (this.backLinkData?.nextButtonLabel) {
      return this.backLinkData.nextButtonLabel;
    }

    return this.pageActionConfig?.nextButtonLabel;
  }

  get backButtonLabel() {
    return this.pageActionConfig?.backButtonLabel;
  }

  get cancelButtonLabel() {
    return this.pageActionConfig?.cancelButtonLabel;
  }

  get pageActionConfig(): PageActionConfig | undefined {
    return this.talyPageDataService.pageData?.pageActionConfig;
  }

  get pageId$() {
    return this.talyPageDataService.pageId$;
  }

  navigateBack() {
    // The Back button has two event listeners attached: One from Angular, one from Trackify.
    // Apparently the browser waits for the first event handler (Angular) to complete before triggering the next one.
    // This leads to the Trackify event ("back button clicked") being fired after the PFE triggers the
    // pageChanged event. This leads to a weird chain of events for the tracking experts.
    // See https://github.developer.allianz.io/gcj/ngx-pfe/issues/681 for details.
    // The slightly hacky solution here is to postpone the Angular event by a single tick, using setTimeout.
    // As an alternative we could trigger the Trackify event manually, but this would require us to
    // add the Trackify library or PFE as a direct dependency to this component (see this commit in PFE:
    // https://github.developer.allianz.io/gcj/ngx-pfe/commit/71d080b32c3ebb5cf5c7548ecde9e3edf5cfaac2).
    window.setTimeout(() => {
      this.frameNavigationService?.navigateBack();
    });
  }

  navigateNext() {
    // The Next button has two event listeners attached: One from Angular, one from Trackify.
    // Apparently the browser waits for the first event handler (Angular) to complete before triggering the next one.
    // This leads to the Trackify event ("next button clicked") being fired after the PFE triggers the
    // pageChanged event. This leads to a weird chain of events for the tracking experts.
    // See https://github.developer.allianz.io/gcj/ngx-pfe/issues/681 for details.
    // The slightly hacky solution here is to postpone the Angular event by a single tick, using setTimeout.
    // As an alternative we could trigger the Trackify event manually, but this would require us to
    // add the Trackify library or PFE as a direct dependency to this component (see this commit in PFE:
    // https://github.developer.allianz.io/gcj/ngx-pfe/commit/71d080b32c3ebb5cf5c7548ecde9e3edf5cfaac2).
    window.setTimeout(() => {
      if (this.backLinkData) {
        window.location.href = this.backLinkData.path;
        return;
      }

      this.frameNavigationService?.navigateNext();
    });
  }

  saveOffer() {
    this.frameNavigationService?.saveOffer();
  }

  cancel() {
    this.frameNavigationService?.cancel();
  }

  triggerCustomAction(customAction: CustomAction) {
    this.businessEventService.handleBusinessEvent({
      handlerType: customAction.handlerType,
      config: customAction.config
    });
  }
}
<!-- TODO: Provide an option to override the default templates. -->
<ng-container *ngTemplateOutlet="isExpert === true ? defaultExpertTemplate : defaultRetailTemplate">
</ng-container>

<ng-template #defaultRetailTemplate>
  <div class="action-button-container">
    @if (!pageActionStatus?.nextHidden) {
    <button
      data-testid="retail-next-button"
      class="button"
      nxButton="block primary"
      (click)="navigateNext()"
      [attr.trackId]="'btn_Nav'"
      [attr.trackValue]="'forward'"
      [disabled]="pageActionStatus?.nextDisabled"
      *aclTag="(pageId$ | async) + '/next-button'"
    >
      {{ (nextButtonLabel | conditionalLabel | async) || defaultNextLabel }}
    </button>
    } @if (pageActionStatus?.showSaveOfferButton) {
    <button
      data-testid="retail-saveOffer-button"
      class="button"
      nxButton="block secondary"
      (click)="saveOffer()"
      [attr.trackId]="'btn_SaveOffer'"
      [attr.trackValue]="'saveOffer'"
      i18n="@@page-action.save-offer-button-label"
      *aclTag="(pageId$ | async) + '/save-offer-button'"
    >
      Save and email proposal
    </button>
    } @if (pageActionStatus?.customActions?.length) { @for (customAction of
    pageActionStatus?.customActions; track customAction.id) {
    <button
      [attr.data-testid]="'retail-' + customAction.id + '-button'"
      class="button"
      nxButton="block secondary"
      (click)="triggerCustomAction(customAction)"
      [attr.trackId]="'btn_' + customAction.id"
      [attr.trackValue]="customAction.id"
      *aclTag="(pageId$ | async) + '/' + customAction.id"
    >
      @if (customAction.icon) {
      <nx-icon [name]="customAction.icon" nxIconPositionStart></nx-icon>
      }
      {{ customAction.label }}
    </button>
    } } @if (!pageActionStatus?.backHidden) {
    <button
      data-testid="retail-back-button"
      class="button"
      nxPlainButton
      (click)="navigateBack()"
      [attr.trackId]="'btn_Nav'"
      [attr.trackValue]="'backward'"
      [disabled]="pageActionStatus?.backDisabled"
      *aclTag="(pageId$ | async) + '/back-button'"
    >
      <nx-icon name="arrow-left" nxIconPositionStart></nx-icon
      >{{ (backButtonLabel | conditionalLabel | async) || defaultBackLabel }}
    </button>
    }
  </div>
</ng-template>

<ng-template #defaultExpertTemplate>
  <div class="buttons-container">
    <div class="extra-button-container">
      @if (pageActionStatus?.showCancelButton) {
      <button
        data-testid="expert-cancel-button"
        nxPlainButton
        (click)="cancel()"
        [attr.trackId]="'btn_Cancel'"
        [attr.trackValue]="'cancel'"
        *aclTag="(pageId$ | async) + '/cancel-button'"
      >
        <nx-icon name="close" nxIconPositionStart></nx-icon>
        {{ (cancelButtonLabel | conditionalLabel | async) || defaultCancelLabel }}
      </button>
      } @if (pageActionStatus?.showSaveOfferButton) {
      <button
        data-testid="expert-saveOffer-button"
        class="button"
        nxPlainButton
        (click)="saveOffer()"
        [attr.trackId]="'btn_SaveOffer'"
        [attr.trackValue]="'saveOffer'"
        *aclTag="(pageId$ | async) + '/save-offer-button'"
      >
        <nx-icon name="save-o" nxIconPositionStart></nx-icon>
        <ng-container i18n="@@page-action.save-offer-button-label">
          Save and email proposal
        </ng-container>
      </button>
      } @if (pageActionStatus?.customActions?.length) { @for (customAction of
      pageActionStatus?.customActions; track customAction) {
      <button
        [attr.data-testid]="'expert-' + customAction.id + '-button'"
        class="button"
        nxPlainButton
        (click)="triggerCustomAction(customAction)"
        [attr.trackId]="'btn_' + customAction.id"
        [attr.trackValue]="customAction.id"
        *aclTag="(pageId$ | async) + '/' + customAction.id"
      >
        @if (customAction.icon) {
        <nx-icon [name]="customAction.icon" nxIconPositionStart></nx-icon>
        }
        {{ customAction.label }}
      </button>
      } }
    </div>
    <div class="action-button-container">
      @if (!pageActionStatus?.nextHidden) {
      <button
        data-testid="expert-next-button"
        class="button"
        nxButton="primary small-medium"
        (click)="navigateNext()"
        [attr.trackId]="'btn_Nav'"
        [attr.trackValue]="'forward'"
        [disabled]="pageActionStatus?.nextDisabled"
        *aclTag="(pageId$ | async) + '/next-button'"
      >
        {{ (nextButtonLabel | conditionalLabel | async) || defaultNextLabel }}
      </button>
      } @if (!pageActionStatus?.backHidden) {
      <button
        data-testid="expert-back-button"
        class="button"
        [nxButton]="
          pageActionStatus?.backButtonUseTertiaryStyle
            ? 'tertiary small-medium'
            : 'secondary small-medium'
        "
        (click)="navigateBack()"
        [attr.trackId]="'btn_Nav'"
        [attr.trackValue]="'backward'"
        [disabled]="pageActionStatus?.backDisabled"
        *aclTag="(pageId$ | async) + '/back-button'"
      >
        {{ (backButtonLabel | conditionalLabel | async) || defaultBackLabel }}
      </button>
      }
    </div>
  </div>
</ng-template>

./actions.component.scss

@use '../../../styles/breakpoints.scss' as *;

:host {
  display: block;
  padding-block: var(--vertical-outer-section-spacing);
  max-width: 100%;
  width: var(--grid-max-width);

  .action-button-container {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    gap: 32px;
    width: 100%;

    @media (min-width: $breakpoint-m) {
      margin-inline: auto;
      min-width: 256px;
      width: max-content;
    }
  }
}

:host-context(.is-stacked),
:host-context(.is-centered) {
  :host {
    margin-inline: auto;
  }
}

:host-context(.is-stacked) {
  :host {
    width: auto;
  }
}

// removes padding-bottom if no buttons are visible
:host(.expert-layout):has(.buttons-container .extra-button-container:empty):has(
    .buttons-container .action-button-container:empty
  ) {
  padding-bottom: 0;
}

:host(.retail-layout):has(.action-button-container:empty) {
  padding-bottom: 0;
}

:host(.expert-layout) {
  .buttons-container {
    display: flex;
    justify-content: space-between;
  }

  .extra-button-container {
    display: flex;
    gap: 32px;
    width: 100%;
  }

  .action-button-container {
    justify-content: flex-start;
    align-items: flex-end;
    flex-direction: row-reverse;
    gap: 16px;
    margin-left: 80px;
  }

  .button {
    min-width: 128px;
  }
}

.button {
  margin: 0;
  min-width: 256px;
}

:host(:empty) {
  padding: 0;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""