0% found this document useful (0 votes)
8 views33 pages

Backup Filter Ben if is Ary

The document contains the implementation of the BsfNormalTransfersComponent, which manages the normal transfer functionality in an Angular application. It utilizes reactive forms to handle user input for transferring funds, including beneficiary selection and payment details, while integrating with various services for data retrieval and error handling. Additionally, there is a spec file for unit testing the component, ensuring its functionality and integration with the necessary services.

Uploaded by

dev
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views33 pages

Backup Filter Ben if is Ary

The document contains the implementation of the BsfNormalTransfersComponent, which manages the normal transfer functionality in an Angular application. It utilizes reactive forms to handle user input for transferring funds, including beneficiary selection and payment details, while integrating with various services for data retrieval and error handling. Additionally, there is a spec file for unit testing the component, ensuring its functionality and integration with the necessary services.

Uploaded by

dev
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 33

bsf-normal-transfer.component.

ts

import { BehaviorSubject, EMPTY, catchError, filter, iif, map, of, switchMap, tap } from 'rxjs';
import { Component, EventEmitter, Output, ChangeDetectionStrategy } from '@angular/core';
import { FormBuilder, FormControl, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { Beneficiary } from '@bsf/beneficiary-manager-http-ang';
import { TransactionQualificationRequest } from '@bsf/payment-support-http-ang';
import { BsfP2pInitiatePaymentService } from '../../services/bsf-p2p-initiate-payment.service';
import {
BeneficiaryCategory,
CustomerSegmentType,
AccountSelectorItem,
TransferTabsTypes,
PurposesListItem,
AmountType,
} from '../../models/bsf-p2p-initiate-payment.model';
import { BsfTransferFormService } from '../../services/bsf-transfer-form.service';
import { paymentAmountValidator } from '../../validators/amount-validator';
import { BeneficiaryTypeDetails } from 'libs/beneficiary-manager-journey/util/src/lib/enums';

@Component({
selector: 'bsf-normal-transfer',
templateUrl: './bsf-normal-transfer.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BsfNormalTransfersComponent {
@Output() transactionQualificationEvent = new EventEmitter<{ reqbody:
TransactionQualificationRequest }>();
private readonly isEditView = this.route.snapshot.queryParams?.isEdit;

readonly accounts$ =
this.bsfP2pInitiatePaymentService.getArrangements(TransferTabsTypes.normal).pipe(
catchError((error) => {
this.error = this.bsfP2pInitiatePaymentService.handleError(error);
return EMPTY;
}),
);
beneficiaryTypeDetails = BeneficiaryTypeDetails;
activeType: string = '';
beneficiariesResponse: Beneficiary[];
currentBeneficiaryType: string = '';
filterBeneficiaries!: Beneficiary[];
beneficiaries$ = new BehaviorSubject<Beneficiary[]>([]);

getActiveBeneficiariesList() {
this.bsfP2pInitiatePaymentService.getActiveBeneficiaries$
.pipe(
tap((results) => {
this.beneficiariesResponse = results;
if (this.isEditView) {
const transferForm = this.bsfP2pInitiatePaymentService.getFormState();
this.transferForm.setValue(transferForm);
this.selectedFromAccount = transferForm.fromAccount;
this.activeType = this.bsfP2pInitiatePaymentService.activeBeneficiaryType;
}
this.beneficiaries$.next(results);
}),
catchError((error) => {
this.error = this.bsfP2pInitiatePaymentService.handleError(error);
return EMPTY;
}),
)
.subscribe();
}

onBeneficiaryTypeFilter(beneficiaryType: string) {
this.setBeneficiaryTypeIcons(beneficiaryType);

if (this.currentBeneficiaryType === beneficiaryType) {


this.currentBeneficiaryType = '';
this.beneficiaries$.next(this.beneficiariesResponse);
} else {
this.currentBeneficiaryType = beneficiaryType;
if (this.beneficiariesResponse) {
this.transferForm.controls.toAccount.reset(undefined, {
emitEvent: false,
});
this.onBeneficiarySelect();
this.filterBeneficiaries = this.beneficiariesResponse?.filter(
(beneficiary) => beneficiary.category === beneficiaryType,
);
}
this.beneficiaries$.next(this.filterBeneficiaries);
}
}

setBeneficiaryTypeIcons(beneficiaryType: string) {
this.beneficiaryTypeDetails.forEach((value) => {
value.iconName =
value.type === beneficiaryType && beneficiaryType !== this.activeType
? value.activeIconName
: value.inActiveIconName;
});
this.activeType = this.activeType === beneficiaryType ? '' : beneficiaryType;
this.bsfP2pInitiatePaymentService.activeBeneficiaryType = this.activeType;
}
// to use enum in template
readonly beneficiaryCategory = BeneficiaryCategory;
error!: { message: string };
selectedFromAccount!: AccountSelectorItem;
readonly transferForm = this.formBuilder.group({
fromAccount: [null as AccountSelectorItem, Validators.required],
toAccount: [null as Beneficiary, Validators.required],
transferAmount: ['', [Validators.required, paymentAmountValidator({ invalidAmount: true })]],
crossCurrencyIntrabank: this.formBuilder.group({
amount: [''],
currency: [''],
amountType: [AmountType.SOURCE],
}),
crossCurrencyInternational: this.formBuilder.group({
payment: {
amount: '',
currency: '',
},
amountType: [AmountType.SOURCE],
isTransferFeeIncluded: [false],
}),
purposeOfPayment: [
{
id: '',
description: '',
isOther: false,
type: '',
},
Validators.required,
],
otherPurpose: [''],
});

readonly toCurrency$ = this.toAccountControl.valueChanges.pipe(


// only applicable for International or Intrabank transfers
filter(
(beneficiary) =>
beneficiary?.category.toLowerCase() === BeneficiaryCategory.int ||
beneficiary?.category.toLowerCase() === BeneficiaryCategory.bsf,
),
switchMap((beneficiary) =>
iif(
() => beneficiary.category.toLowerCase() === BeneficiaryCategory.int,
this.bsfP2pInitiatePaymentService.getCurrencies(beneficiary.bankCountry,
beneficiary.bankCode),
this.bsfP2pInitiatePaymentService.getBsfAccountCurrency(beneficiary.accountNumber),
).pipe(
map((currencies) =>
'allowedCurrencies' in currencies ? currencies.allowedCurrencies : currencies.currencyCode,
),
tap((currencies) => {
// set TARGET currency based on beneficiary
if (beneficiary.category.toLowerCase() === BeneficiaryCategory.int) {
const amount =
this.transferForm.get('crossCurrencyInternational.payment').value?.amount;
!this.isEditView ||
!this.transferForm.controls.crossCurrencyInternational.controls.payment.value ||
(this.isEditView &&
!this.transferForm.controls.crossCurrencyInternational.controls.payment.value.amount &&
!this.transferForm.controls.crossCurrencyInternational.controls.payment.value.currency)
? this.transferForm.get('crossCurrencyInternational.payment').patchValue({
amount,
currency: currencies[0],
})
: this.transferForm.get('crossCurrencyInternational.payment').patchValue({
amount,
currency:
this.transferForm.controls.crossCurrencyInternational.controls.payment.value?.currency,
});
this.transferForm.controls.crossCurrencyIntrabank.controls.amount.clearValidators();

this.transferForm.controls.crossCurrencyIntrabank.controls.amount.updateValueAndValidity();
} else {
this.transferForm.get('crossCurrencyIntrabank.currency').patchValue(currencies as string);
this.transferForm.controls.crossCurrencyInternational.controls.payment.clearValidators();

this.transferForm.controls.crossCurrencyInternational.controls.payment.updateValueAndValidity();
}
}),
catchError((error) => {
this.error = this.bsfP2pInitiatePaymentService.handleError(error);
// return empty "toCurrency" for simple amount group component to load
return of([]);
}),
),
),
);

readonly purposeOfPayment$ = this.toAccountControl.valueChanges.pipe(


switchMap((beneficiary) => {
const paymentCategory =
this.bsfP2pInitiatePaymentService.getPaymentCategory(beneficiary.category);
return this.bsfP2pInitiatePaymentService.getPurposes(paymentCategory,
TransferTabsTypes.normal).pipe(
tap((purposes: PurposesListItem[]) => {
// TODO: revisit below when refactoring p2p-initiate-payment.service
// Service returns error as a successful emitted value instead of a handleError message
// Below check is similar to if ('error' in purposes)
if (!Array.isArray(purposes)) {
this.error = this.bsfP2pInitiatePaymentService.handleError(purposes);
} else {
// select personal purpose of payment by default if it is not selected (via edit operation)
if (!this.transferForm.controls.purposeOfPayment.value) {
const personalPurpose = purposes.find((purpose) =>
purpose.description.includes('Personal'));
this.transferForm.patchValue({ purposeOfPayment: personalPurpose });
}
}
}),
// TODO: revisit below when refactoring p2p-initiate-payment.service
// need to map purposes to empty array if there is an error
map((purposes) => (Array.isArray(purposes) ? purposes : [])),
);
}),
);

get fromAccountControl(): FormControl {


return this.transferForm.get('fromAccount') as FormControl;
}

get toAccountControl(): FormControl {


return this.transferForm.get('toAccount') as FormControl;
}

get transferAmountControl(): FormControl {


return this.transferForm.get('transferAmount') as FormControl;
}

constructor(
private readonly route: ActivatedRoute,
private readonly formBuilder: FormBuilder,
private readonly temporaryFormService: BsfTransferFormService,
private readonly bsfP2pInitiatePaymentService: BsfP2pInitiatePaymentService,
){
this.getActiveBeneficiariesList();
if (!this.isEditView) {
this.setBeneficiaryTypeIcons(this.activeType);
}
}

hideAlert() {
this.error = undefined;
}

onBeneficiarySelect() {
this.transferAmountControl.reset();
this.fromAccountControl.reset(undefined, {
emitEvent: false,
});
this.transferForm.controls.purposeOfPayment.reset();
this.transferForm.controls.otherPurpose.reset();
this.transferForm.controls.crossCurrencyInternational.controls.payment.reset();
this.transferForm.controls.crossCurrencyIntrabank?.controls?.amount.reset();
this.transferForm.controls.transferAmount.setValidators([
Validators.required,
paymentAmountValidator({ invalidAmount: true }),
]);
this.transferForm.updateValueAndValidity();
this.selectedFromAccount = undefined;
}

onFromAccountSelect(account: AccountSelectorItem) {
this.selectedFromAccount = account;
}

onPaymentContinue() {
const debitAccount = this.fromAccountControl.value?.number;
const creditAccount = this.toAccountControl.value?.accountNumber;
const bankCode = this.toAccountControl.value?.bankCode;
const amount = this.transferForm.get('transferAmount').value;

const requestParam = {
customerSegment: CustomerSegmentType.CLC,
debitAccount,
creditAccount,
bankCode,
amount,
transactionType: TransactionQualificationRequest.TransactionTypeEnum.IBAN,
};

this.makePaymentReviewWork();
this.bsfP2pInitiatePaymentService.setFormState(this.transferForm.value);

// check for transaction qualification (only applicable for Domestic Transfers, check logic inside
container component)
this.transactionQualificationEvent.emit({
reqbody: requestParam,
});
}

resetForm() {
this.transferForm.reset(undefined, { emitEvent: false });
this.selectedFromAccount = undefined;
this.setBeneficiaryTypeIcons('');
}

/**
* TODO:
* Below method is to isolate technical debt which is needed to make payment review work
correctly.
* Can be removed when payment container, review component and initiate payment service is
refactored to remove state manipulation.
*/
private makePaymentReviewWork() {
this.bsfP2pInitiatePaymentService.selectedBeneficiary = this.toAccountControl.value;
this.bsfP2pInitiatePaymentService.selectedItems = this.fromAccountControl.value;
this.temporaryFormService.normalTransferForm.controls.transferAmount.setValue(
this.transferForm.get('transferAmount').value,
);
this.temporaryFormService.normalTransferForm.controls.transferPurpose.setValue(
this.transferForm.get('purposeOfPayment').value,
);
this.temporaryFormService.normalTransferForm.controls.isTransferFeeIncluded.setValue(
this.transferForm.controls.crossCurrencyInternational.controls.isTransferFeeIncluded.value,
);

/* TODO : Form needs to be revisited/refactored


* Form state not setting correctly on changing from account as amount controls change
*/
if (
!this.transferForm.controls.crossCurrencyIntrabank?.controls?.currency.value &&
this.toAccountControl.value.category.toLowerCase() === this.beneficiaryCategory.bsf
){
this.transferForm.controls.crossCurrencyIntrabank?.controls?.currency.setValue(
this.fromAccountControl.value?.currency,
);

this.transferForm.controls.crossCurrencyIntrabank?.controls?.amountType.value
? this.transferForm.controls.crossCurrencyIntrabank?.controls?.amountType.value
:
this.transferForm.controls.crossCurrencyIntrabank?.controls?.amountType.setValue(AmountType.S
OURCE);
}
}
}

bsf-normal-transfer.component.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing';


import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

import { BsfNormalTransfersComponent } from './bsf-normal-transfer.component';


import { ActivatedRoute } from '@angular/router';
import { BsfTransferFormService } from '../../services/bsf-transfer-form.service';
import { BsfP2pInitiatePaymentService } from '../../services/bsf-p2p-initiate-payment.service';
import { of } from 'rxjs';
import { BeneficiaryCategory } from '@bsf/beneficiary-manager-journey-util';

const purposes = [
{
id: '6',
type: 'TRANSFER',
isOther: false,
description: 'Personal / Family Remittance',
},
{
id: '1',
type: 'TRANSFER',
isOther: false,
description: 'Import Finance Payment',
},
{
id: '2',
type: 'TRANSFER',
isOther: false,
description: 'Education / Medical / Travel Expenses',
},
{
id: '7',
type: 'TRANSFER',
isOther: true,
description: 'Others',
},
];

const beneficiary = {
cpt: '011988',
beneficiarySequence: '7772370',
category: BeneficiaryCategory.INT,
active: false,
beneficiaryName: 'International',
alias: null,
beneficiaryCountry: 'UNITED KINGDOM',
beneficiaryAddress: 'Qa',
accountNumber: 'GB33MIDL40060751663739',
bankName: 'HSBC BANK PLC',
bankBranch: 'HSBC BANK PLC',
bankAddressLine1: '62 - 76 PARK STREET',
bankAddressLine2: '',
bankCode: 'MIDLGB2137G',
bankCountry: 'GB',
isIban: false,
createdUser: 'MBK',
createdDate: '2024-01-11',
modifiedUser: 'MBK',
modifiedDate: '2024-01-11',
};

const mockForm = {
fromAccount: {
id: '123',
balance: 100,
favorite: false,
bankBranchCode: 'abc',
name: 'Savings Account',
number: 'SA5655000000001198802533',
},
toAccount: {
cpt: '011988',
beneficiarySequence: '7772370',
category: BeneficiaryCategory.BSF,
active: false,
beneficiaryName: 'International',
alias: null,
beneficiaryCountry: 'UNITED KINGDOM',
beneficiaryAddress: 'Qa',
accountNumber: 'GB33MIDL40060751663739',
bankName: 'HSBC BANK PLC',
bankBranch: 'HSBC BANK PLC',
bankAddressLine1: '62 - 76 PARK STREET',
bankCode: 'MIDLGB2137G',
isIban: false,
createdUser: 'MBK',
createdDate: '2024-01-11',
modifiedUser: 'MBK',
modifiedDate: '2024-01-11',
},
transferAmount: '100',
crossCurrencyIntrabank: {
amount: '100',
currency: 'USD',
amountType: 'type1',
},
crossCurrencyInternational: {
payment: {
amount: '200',
currency: 'EUR',
},
amountType: 'type2',
isTransferFeeIncluded: false,
},
purposeOfPayment: {
id: '1',
description: 'Personal',
isOther: false,
type: '',
},
otherPurpose: 'Other purpose',
};
const spyBsfP2pInitiatePaymentService = jasmine.createSpyObj('BsfP2pInitiatePaymentService', [
'activeBeneficiaryType'
]);

describe('BsfNormalTransfersComponent', () => {
let component: BsfNormalTransfersComponent;
let fixture: ComponentFixture<BsfNormalTransfersComponent>;
const mockActivatedRoute = {
snapshot: {
queryParams: { isEdit: false },
},
};

const mockBsfP2pInitiatePaymentService = {
getArrangements: () => of([]),
handleError: () => ({ message: 'Sorry, this service is currently unavailable. Please try again
later.' }),
getActiveBeneficiaries$: of([beneficiary]),
getCurrencies: () => of({ allowedCurrencies: ['USD'] }),
getBsfAccountCurrency: () => of({ currencyCode: 'SAR' }),
getPaymentCategory: () => 'category',
getPurposes: () => of([{ id: '1', description: 'Personal', isOther: false, type: '' }]),
getFormState: () => mockForm,
setFormState: jasmine.createSpy('setFormState'),
selectedBeneficiary: {},
selectedItems: {},
};

const mockBsfTransferFormService = {
normalTransferForm: {
controls: {
transferAmount: { setValue: jasmine.createSpy('setValue') },
transferPurpose: { setValue: jasmine.createSpy('setValue') },
otherPurpose: { setValue: jasmine.createSpy('setValue') },
},
},
};

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [BsfNormalTransfersComponent],
providers: [
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
{
provide: FormBuilder,
useValue: {
group() {
return new FormGroup({
fromAccount: new FormControl(''),
toAccount: new FormControl(''),
transferAmount: new FormControl(''),
crossCurrencyIntrabank: new FormGroup({
amount: new FormControl(''),
currency: new FormControl(''),
amountType: new FormControl(''),
}),
crossCurrencyInternational: new FormGroup({
payment: new FormControl({
amount: '',
currency: '',
}),
amountType: new FormControl(''),
isTransferFeeIncluded: new FormControl(false),
}),
purposeOfPayment: new FormControl({
id: '',
description: '',
isOther: false,
type: '',
}),
otherPurpose: new FormControl(''),
});
},
},
},
{ provide: BsfP2pInitiatePaymentService, useValue: mockBsfP2pInitiatePaymentService },
{ provide: BsfTransferFormService, useValue: mockBsfTransferFormService },
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();

fixture = TestBed.createComponent(BsfNormalTransfersComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {


expect(component).toBeTruthy();
});

it('should set the first currency returned in list of currencies when toCurrency$ is subscribed to
and beneficiary is international', () => {
spyOn(mockBsfP2pInitiatePaymentService, 'getCurrencies').and.returnValue(
of({ allowedCurrencies: ['USD', 'EUR', 'SAR'] }),
);
component.transferForm.get('toAccount').setValue(beneficiary as any);
component.toCurrency$.subscribe();
expect(component.transferForm.get('crossCurrencyInternational.payment').value).toEqual({
amount: '',
currency: 'USD',
});
});

it('should set iconName to activeIconName for the matching beneficiaryType and inactive for
others', () => {
component.setBeneficiaryTypeIcons('BSF');
expect(component.beneficiaryTypeDetails[0].iconName).toBe('benf-bsf-white');
expect(component.beneficiaryTypeDetails[1].iconName).toBe('benf-ksa');
});

it('should update bsfP2pInitiatePaymentService.activeBeneficiaryType to activeType', () => {


component.setBeneficiaryTypeIcons('BSF');
expect(spyBsfP2pInitiatePaymentService.activeBeneficiaryType).not.toBe('BSF');
});

it('should set the currency returned in list of currencies when toCurrency$ is subscribed to and
beneficiary is intrabank', () => {
const intrabankBeneficiary = {
cpt: '011988',
beneficiarySequence: '7772370',
category: BeneficiaryCategory.BSF,
active: false,
beneficiaryName: 'International',
alias: null,
beneficiaryCountry: 'UNITED KINGDOM',
beneficiaryAddress: 'Qa',
accountNumber: 'GB33MIDL40060751663739',
bankName: 'HSBC BANK PLC',
bankBranch: 'HSBC BANK PLC',
bankAddressLine1: '62 - 76 PARK STREET',
bankAddressLine2: '',
bankCode: 'MIDLGB2137G',
bankCountry: 'GB',
isIban: false,
createdUser: 'MBK',
createdDate: '2024-01-11',
modifiedUser: 'MBK',
modifiedDate: '2024-01-11',
};
spyOn(mockBsfP2pInitiatePaymentService, 'getBsfAccountCurrency').and.returnValue(of({
currencyCode: 'SAR' }));
component.transferForm.get('toAccount').setValue(intrabankBeneficiary as any);
component.toCurrency$.subscribe();
expect(component.transferForm.get('crossCurrencyIntrabank.currency').value).toEqual('SAR');
});

it('should set the purpose of payment with a default value as Personal', () => {
spyOn(mockBsfP2pInitiatePaymentService, 'getPurposes').and.returnValue(of(purposes));
component.transferForm.get('toAccount').setValue(beneficiary as any);
component.purposeOfPayment$.subscribe();
expect(component.transferForm.get('purposeOfPayment').value).toEqual({
id: '6',
description: 'Personal / Family Remittance',
isOther: false,
type: 'TRANSFER',
});
});

it('should set the purpose of payment with a default value as Personal', () => {
spyOn(mockBsfP2pInitiatePaymentService, 'getPurposes').and.returnValue(of(null));
component.transferForm.get('toAccount').setValue(beneficiary as any);
component.purposeOfPayment$.subscribe();
expect(component.error).toEqual({
message: `Sorry, this service is currently unavailable. Please try again later.`,
});
});

it('should hide alert when the cross icon is clicked', () => {


component.hideAlert();
expect(component.error).toBeUndefined();
});

it('should reset form fields except toAccount control when a beneficiary is selected', () => {
spyOn(component.transferAmountControl, 'reset');
spyOn(component.fromAccountControl, 'reset');
spyOn(component.transferForm.get('purposeOfPayment'), 'reset');
spyOn(component.transferForm.get('otherPurpose'), 'reset');

component.onBeneficiarySelect();

expect(component.transferAmountControl.reset).toHaveBeenCalled();
expect(component.fromAccountControl.reset).toHaveBeenCalledWith(undefined, { emitEvent:
false });
expect(component.transferForm.get('purposeOfPayment').reset).toHaveBeenCalled();
expect(component.transferForm.get('otherPurpose').reset).toHaveBeenCalled();
expect(component.selectedFromAccount).toBeUndefined();
});

it('should set the from account property when an account is selected from the dropdown', () => {
const account = {
favorite: false,
bankBranchCode: '123',
id: '456',
balance: 100.23,
};
component.onFromAccountSelect(account);
expect(component.selectedFromAccount).toEqual(account);
});

it('should reset the form when reset button is clicked', () => {


spyOn(component.transferForm, 'reset');
component.resetForm();
expect(component.transferForm.reset).toHaveBeenCalled();
expect(component.selectedFromAccount).toBeUndefined();
component.setBeneficiaryTypeIcons('');
expect(component.beneficiaryTypeDetails[0].iconName).toBe('benf-bsf');
});

it('should set iconName to activeIconName for matching type and inactive for others', () => {
component.onBeneficiaryTypeFilter('BSF');
expect(component.beneficiaryTypeDetails[0].iconName).toBe('benf-bsf-white');
expect(component.beneficiaryTypeDetails[1].iconName).toBe('benf-ksa');
});

it('should toggle activeType correctly', () => {


component.onBeneficiaryTypeFilter('BSF');
expect(component.activeType).toBe('BSF');
});

it('should change iconName based on the new beneficiaryType and reset icons if same type is
selected again', () => {
component.onBeneficiaryTypeFilter('BSF');
expect(component.beneficiaryTypeDetails[0].iconName).toBe('benf-bsf-white');
component.onBeneficiaryTypeFilter('BSF');
expect(component.beneficiaryTypeDetails[0].iconName).toBe('benf-bsf');
});
});

bsf-normal-transfer.component.html

<div class="bb-card">
<div class="bb-card__body">
<bb-alert-ui
*ngIf="error"
i18n-title="Service error@@bsf-retail.p2p-payment-journey.transfer.service.error"
title="Service error alert"
[dismissible]="true"
(close)="hideAlert()"
>
<p>{{ error.message }}</p>
</bb-alert-ui>

<ng-container *ngIf="{ accounts: accounts$ | async, beneficiaries: beneficiaries$ | async } as


transfer">
<div
class="beneficiary-text-support"
data-role="beneficiary-type"
i18n="Select a Beneficiary type@@bsf-retail.beneficiary-manager.beneficiary-list-
container.div.beneficiary-type"
>
Select a beneficiary type:
</div>
<div class="custom-beneficiary-type bb-block--lg">
<button
type="button"
i18n="Beneficiary types@@bsf-retail.beneficiary-manager.beneficiary-list-
container.button.beneficiary-type"
data-role="beneficiary-type-button"
class="custom-beneficiary-type-btn"
[ngClass]="{
active: beneficiaryType.type === activeType,
inactive: beneficiaryType.type !== activeType
}"
*ngFor="let beneficiaryType of beneficiaryTypeDetails"
(click)="onBeneficiaryTypeFilter(beneficiaryType.type)"
>
<bb-icon-ui
class="bb-inline-stack__item bb-navigation-item__icon"
[name]="'bsf-' + beneficiaryType.iconName + '-icon'"
size="sm"
></bb-icon-ui>
{{ beneficiaryType.name }}
</button>
</div>
<form [formGroup]="transferForm">
<!-- Initiator Account -->
<div class="bb-block bb-block--lg" *ngIf="toAccountControl.valid">
<bsf-transfers-initiator-selector
[accounts]="transfer.accounts"
[beneficiaryCategory]="toAccountControl.value.category"
[selectedFromAccount]="selectedFromAccount"
[fromAccount]="fromAccountControl"
(selectedInitiator)="onFromAccountSelect($event)"
></bsf-transfers-initiator-selector>
</div>

<!-- To Account -->


<ng-container *ngIf="transfer.beneficiaries; else loading">
<bsf-transfers-beneficiary-selector
[beneficiaries]="transfer.beneficiaries"
[toAccountControl]="toAccountControl"
(selectedBeneficiary)="onBeneficiarySelect()"
></bsf-transfers-beneficiary-selector>
</ng-container>

<!-- Domestic Payments Amount -->

<ng-container *ngIf="toAccountControl.value?.category.toLowerCase() ===


beneficiaryCategory.ksa">
<div class="mb-4 bb-block--lg" *ngIf="fromAccountControl.valid">
<h3
class="division-separator"
i18n="Payment Details@@bsf-retail.p2p-payment-journey.transfer.form.amount.heading"
>
Payment details
</h3>
<ng-template [ngTemplateOutlet]="simpleAmountGroup"></ng-template>
</div>
</ng-container>
<!-- Intrabank + International Payments Amount -->
<ng-container *ngIf="toCurrency$ | async as toCurrency">
<ng-container *ngIf="fromAccountControl.valid">
<div class="mt-5 mb-4 bb-block--lg">
<h3
class="division-separator"
i18n="Payment Details@@bsf-retail.p2p-payment-
journey.transfer.form.amount.heading"
>
Payment details
</h3>

<!-- Intrabank component -->


<ng-container *ngIf="toAccountControl.value.category.toLowerCase() ===
beneficiaryCategory.bsf">
<!-- Show simple currency group if: -->
<!-- 1. toCurrency fails to load -->
<!-- 2. toCurrency is the same as fromCurrency -->
<ng-container
*ngIf="toCurrency.length && toCurrency !== fromAccountControl.value?.currency; else
simpleAmountGroup"
>
<bsf-cross-currency-intratransfer-amount-group

[crossCurrencyAmountControl]="transferForm.controls.crossCurrencyIntrabank?.controls?.amount"

[crossCurrencytoCurrencyControl]="transferForm.controls.crossCurrencyIntrabank?.controls?.curre
ncy"
[crossCurrencyAmountTypeControl]="
transferForm.controls.crossCurrencyIntrabank?.controls?.amountType
"
[fromAccountControl]="transferForm.controls.fromAccount"
[transferAmountControl]="transferAmountControl"
></bsf-cross-currency-intratransfer-amount-group>
</ng-container>
</ng-container>

<!-- International component -->


<ng-container *ngIf="toAccountControl.value.category.toLowerCase() ===
beneficiaryCategory.int">
<bsf-cross-currency-international-amount-group
[beneficiaryId]="toAccountControl.value.beneficiarySequence"

[crossCurrencyPaymentControl]="transferForm.controls.crossCurrencyInternational.controls.payme
nt"
[crossCurrencyAmountTypeControl]="
transferForm.controls.crossCurrencyInternational.controls.amountType
"
[crossCurrencyTransferFeeControl]="
transferForm.controls.crossCurrencyInternational.controls.isTransferFeeIncluded
"
[fromAccountControl]="fromAccountControl"
[toCurrencies]="toCurrency"
[transferAmountControl]="transferAmountControl"
></bsf-cross-currency-international-amount-group>
</ng-container>
</div>
</ng-container>
</ng-container>

<!-- Purpose -->


<ng-container *ngIf="purposeOfPayment$ | async as purposes">
<ng-container *ngIf="fromAccountControl.valid && toAccountControl.valid">
<bsf-transfers-purpose-of-payment
[purposes]="purposes"
[purposeOfPayment]="transferForm.controls.purposeOfPayment"
[otherPurpose]="transferForm.controls.otherPurpose"
></bsf-transfers-purpose-of-payment>
</ng-container>
</ng-container>

<!-- Footer Section -->


<div class="d-flex justify-content-between bb-block--md bb-stack bb-stack--wrap product-
item-content pt-5">
<div class="bb-stack__item">
<button
bbButton
type="button"
class="bb-button-bar__button bb-load-button btn-secondary btn-md btn"
i18n="Cancel @@bsf-retail.p2p-payment-journey.transfer.form.button.cancel"
(click)="resetForm()"
>
Cancel
</button>
</div>
<div class="bb-stack__item bb-text-align-right">
<button
bbButton
type="button"
class="bb-button-bar__button bb-load-button btn-primary btn-md btn"
[disabled]="transferForm.invalid"
i18n="Continue @@bsf-retail.p2p-payment-journey.transfer.form.button.continue"
(click)="onPaymentContinue()"
>
Continue
</button>
</div>
</div>
</form>
</ng-container>
</div>
</div>

<ng-template #simpleAmountGroup>
<bsf-transfers-simple-amount-group
[transferAmount]="transferForm.controls.transferAmount"
[currency]="fromAccountControl.value?.currency"
></bsf-transfers-simple-amount-group>
</ng-template>

<ng-template #loading>
<bb-loading-indicator-ui loaderSize="lg"></bb-loading-indicator-ui>
</ng-template>

beneficiaryTypes.ts - /Users/dthorat/Documents/Projects/web-retail-us-portalless/src/libs/
beneficiary-manager-journey/util/src/lib/enums/beneficiaryTypes.ts

import '@angular/localize/init';
export const BeneficiaryTypeDetails = [
{
name: $localize`:BSF@@bsf-retail.beneficiary-manager.beneficiary-list-container.beneficiary-
type.label.within-bsf:BSF`,
type: 'BSF',
iconName: 'benf-bsf',
activeIconName: 'benf-bsf-white',
inActiveIconName: 'benf-bsf',
},
{
name: $localize`:Local@@bsf-retail.beneficiary-manager.beneficiary-list-container.beneficiary-
type.label.local-transfer:Local`,
type: 'KSA',
iconName: 'benf-ksa',
activeIconName: 'benf-ksa-white',
inActiveIconName: 'benf-ksa',
},
{
name: $localize`:International@@bsf-retail.beneficiary-manager.beneficiary-list-
container.beneficiary-type.label.international:International`,
type: 'INT',
iconName: 'benf-int',
activeIconName: 'benf-int-white',
inActiveIconName: 'benf-int',
},
{
name: $localize`:Western union@@bsf-retail.beneficiary-manager.beneficiary-list-
container.beneficiary-type.label.western-union:Western union`,
type: 'WU',
iconName: 'benf-wu',
activeIconName: 'benf-wu-white',
inActiveIconName: 'benf-wu',
},
{
name: $localize`:Brokerage@@bsf-retail.beneficiary-manager.beneficiary-list-
container.beneficiary-type.label.brokerage:Brokerage`,
type: 'BROKERAGE',
iconName: 'benf-brokerage',
activeIconName: 'benf-brokerage-white',
inActiveIconName: 'benf-brokerage',
},
{
name: $localize`:Company@@bsf-retail.beneficiary-manager.beneficiary-list-
container.beneficiary-type.label.company:Company`,
type: 'COMPANY',
iconName: 'benf-company',
activeIconName: 'benf-company-white',
inActiveIconName: 'benf-company',
},
];

src/libs/beneficiary-manager-journey/util/src/lib/enums/index.ts
export * from './accountCreateType';
export * from './view';
export * from './beneficiaryActions';
export * from './beneficiaryTypes';

beneficiary-list-container.component.html

<div class="bb-card" data-role="loading-label">


<div class="bb-card__body">
<ng-container *ngIf="items | async as items; else loadingTemplate">
<ng-container *ngIf="beneficiaries$ | async as beneficiaries">
<div
class="bb-block bb-block--xs"
[ngClass]="{
'd-none d-lg-block': (isListView | async) !== true
}"
>
<div class="row">
<div class="col bb-stack">
<div class="bb-stack__item bb-stack__item--fill">
<bb-search-box-ui
*ngIf="isSearchBoxVisible"
class="bb-toolbar__item"
data-role="beneficiary-manager-search"
placeholder="Search"
i18n-placeholder="
Search box placeholder | Placeholder for search box@@beneficiary-
manager.placeholder.search"
aria-label="Search"
i18n-aria-label="@@beneficiary-manager.placeholder.search"
[showSearch]="true"
[showClear]="true"
[formControl]="searchTerm"
(valueChange)="onSearchTermChange($event)"
(submit)="searchBeneficiaries(searchTerm.value)"
(keyup.enter)="searchBeneficiaries(searchTerm.value)"
(clear)="clearSearch()"
></bb-search-box-ui>
<div
class="beneficiary-text-support"
data-role="beneficiary-type"
i18n="
Select a Beneficiary
type@@bsf-retail.beneficiary-manager.beneficiary-list-container.div.beneficiary-type"
>
Select a beneficiary type:
</div>
<div class="custom-beneficiary-type bb-block--lg">
<button
type="button"
i18n="
Beneficiary
types@@bsf-retail.beneficiary-manager.beneficiary-list-container.button.beneficiary-
type"
data-role="beneficiary-type-button"
class="custom-beneficiary-type-btn"
[ngClass]="{
active: beneficiaryType.type === activeType,
inactive: beneficiaryType.type !== activeType
}"
*ngFor="let beneficiaryType of beneficiaryTypeDetails"
(click)="onBeneficiaryTypeFilter(beneficiaryType.type)"
>
<bb-icon-ui
class="bb-inline-stack__item bb-navigation-item__icon"
[name]="'bsf-' + beneficiaryType.iconName + '-icon'"
size="sm"
></bb-icon-ui>
{{ beneficiaryType.name }}
</button>
</div>
<div class="sr-only" aria-live="assertive">
<p i18n="@@beneficiary-manager.list.results-found">
{beneficiaries.length || 0, plural, =1 {1 result found} other
{{{beneficiaries.length}} results found}}
</p>
</div>
</div>
</div>

<ng-template #rt let-result="result" let-term="term">


<div class="text-truncate" [title]="result">
<ngb-highlight [result]="result" [term]="term"></ngb-highlight>
</div>
</ng-template>
</div>
</div>

<div *ngIf="(isListView | async) !== true" class="row d-block d-lg-none">


<button bbButton color="unstyled" (click)="backToList()" class="bb-text-bold text-
primary">
<bb-icon-ui name="angle-left"></bb-icon-ui>

<span i18n="Back to list view|Span for going back to list view@@beneficiary-


manager.span.backToList">
Back to list
</span>
</button>
</div>

<div
class="row bb-block bb-block--md beneficiary-all-beneficiaries-container"
*ngIf="beneficiaries.length > 0 || firstBeneficiaryEdit; else emptyTemplate"
>
<div
class="col beneficiary-list-container"
[ngClass]="{
'd-block': isListView | async,
'd-lg-block d-none': (isListView | async) === false
}"
>
<bb-beneficiary-list
[items]="beneficiaries"
[selectedItemId]="(selectedItemId | async) || ''"
(selectedChange)="select($event)"
>
</bb-beneficiary-list>
</div>

<div
#detailsContainer
class="col col-lg-6 beneficiary-details-container"
[ngClass]="{
'd-none d-lg-block': isListView | async
}"
>
<ng-content></ng-content>
</div>
</div>
</ng-container>
</ng-container>
</div>
</div>

<ng-template #loadingTemplate>
<ng-container *ngIf="!error; else errorTemplate">
<div class="bb-state-container">
<bb-loading-indicator-ui
i18n-text="@@beneficiary-manager.loading.label"
text="Loading beneficiaries..."
data-role="beneficiary-manager-loading"
></bb-loading-indicator-ui>
</div>
</ng-container>
</ng-template>

<ng-template #emptyTemplate>
<ng-container *ngIf="!searchTerm.value; else emptySearchTemplate">
<div class="bb-state-container">
<bb-empty-state-ui
i18n-title="@@beneficiary-manager.state.noBeneficiaryLoaded.title"
title="No beneficiary"
i18n-subtitle="@@beneficiary-manager.state.noBeneficiaryLoaded.subtitle"
subtitle="You don't have any beneficiary to display yet.<br>Try adding a new beneficiary
using the 'New Beneficiary' button"
iconClasses="d-flex justify-content-center mb-3"
iconModifier="beneficiaries"
iconColor="primary"
iconSize="xxl"
data-role="beneficiary-manager-empty"
></bb-empty-state-ui>
</div>
</ng-container>
</ng-template>

<ng-template #emptySearchTemplate>
<div class="bb-state-container">
<bb-empty-state-ui
i18n-title="@@beneficiary-manager.state.empty-search.title"
title="No results found"
i18n-subtitle="@@beneficiary-manager.state.empty-search.subtitle"
subtitle="We can't find what you're looking for. Please try a different criteria."
iconClasses="d-flex justify-content-center mb-3"
iconModifier="search"
iconColor="primary"
iconSize="xxl"
data-role="beneficiary-manager-list-search-empty"
></bb-empty-state-ui>
</div>
</ng-template>

<ng-template #errorTemplate>
<bb-alert-ui
*ngIf="listError.shouldShow"
[message]="listError.message"
title=""
[dismissible]="true"
(close)="onAlertClose()"
>
</bb-alert-ui>
</ng-template>

beneficiary-list-container.component.spec.ts

import { NO_ERRORS_SCHEMA } from '@angular/core';


import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import {
BeneficiaryNavigationService,
BeneficiaryService,
BeneficiaryManagerJourneyConfigurationService,
} from '@bsf/beneficiary-manager-journey-data-access';
import { View } from '@bsf/beneficiary-manager-journey-util';
import { of, Subject } from 'rxjs';

import { BeneficiaryListContainerComponent } from './beneficiary-list-container.component';

describe('BeneficiaryListContainerComponent', () => {
let component: BeneficiaryListContainerComponent;
let fixture: ComponentFixture<BeneficiaryListContainerComponent>;
const paramsSource = new Subject<ParamMap>();

const activatedRouteStub = {
queryParamMap: paramsSource.asObservable(),
};
let injectedNavigationService: BeneficiaryNavigationService;

beforeEach(() => {
jest.resetAllMocks();
const beneficiaryServiceStub = {
updateItemList: () => ({}),
dataService: {
getBeneficiaries: () => of({ body: [] }),
},
items: of({ beneficiaries: [] }),
getAccountType: () => of('IBAN'),
};
const BeneficiaryNavigationServiceStub = {
list: () => ({}),
select: () => ({}),
currentView: {},
edit: () => ({}),
newBeneficiary: () => ({}),
};
const routerStub = {
events: of('load-more'),
};
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [BeneficiaryListContainerComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [
{ provide: BeneficiaryService, useValue: beneficiaryServiceStub },
{ provide: BeneficiaryNavigationService, useValue: BeneficiaryNavigationServiceStub },
{ provide: Router, useValue: routerStub },
{ provide: ActivatedRoute, useValue: activatedRouteStub },
BeneficiaryManagerJourneyConfigurationService,
],
});
fixture = TestBed.createComponent(BeneficiaryListContainerComponent);
TestBed.inject(BeneficiaryService);
injectedNavigationService = TestBed.inject(BeneficiaryNavigationService);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('can load instance', () => {


expect(component).toBeTruthy();
});

it('firstBeneficiaryEdit defaults to: false', () => {


expect(component.firstBeneficiaryEdit).toEqual(false);
});

it('should invoke service on search', () => {


const listSpy = jest.spyOn(injectedNavigationService, 'list');
component.searchBeneficiaries('john');
expect(listSpy).toHaveBeenCalledWith('john');
});

describe('backToList', () => {
it('makes expected calls', () => {
const BeneficiaryNavigationServiceStub: BeneficiaryNavigationService =
fixture.debugElement.injector.get(BeneficiaryNavigationService);
jest.spyOn(BeneficiaryNavigationServiceStub, 'list');
component.backToList();
expect(BeneficiaryNavigationServiceStub.list).toHaveBeenCalled();
});
});

xit('newBeneficiary should call navigation service newBeneficiaryForm', () => {


jest.spyOn(injectedNavigationService, 'newBeneficiaryForm');
component.newBeneficiary(1);
expect(injectedNavigationService.newBeneficiaryForm).toHaveBeenCalled();
expect(injectedNavigationService.currentView).toBe(View.Edit);
});

xit('onSearchTermChange should schedule next search', fakeAsync(() => {


const navigationSpy = jest.spyOn(injectedNavigationService, 'list');
component.onSearchTermChange('query');
expect(navigationSpy).not.toHaveBeenCalled();
tick(400);
expect(navigationSpy).toHaveBeenCalledWith('query');
}));

xit('clearSearch should schedule next empty search', fakeAsync(() => {


const navigationSpy = jest.spyOn(injectedNavigationService, 'list');
component.clearSearch();
expect(navigationSpy).not.toHaveBeenCalled();
tick(400);
expect(navigationSpy).toHaveBeenCalledWith('');
}));

xit('searchBeneficiaries should delegate phrase to search services', () => {


const navigationSpy = jest.spyOn(injectedNavigationService, 'list');
component.searchBeneficiaries('query');
expect(navigationSpy).toHaveBeenCalledWith('query');
});

it('should set iconName to activeIconName for matching type and inactive for others', () => {
component.onBeneficiaryTypeFilter('BSF');
expect(component.beneficiaryTypeDetails[0].iconName).toBe('benf-bsf-white');
expect(component.beneficiaryTypeDetails[1].iconName).toBe('benf-ksa');
});

it('should toggle activeType correctly', () => {


component.onBeneficiaryTypeFilter('BSF');
expect(component.activeType).toBe('BSF');
});

it('should change iconName based on the new beneficiaryType and reset icons if same type is
selected again', () => {
component.onBeneficiaryTypeFilter('BSF');
expect(component.beneficiaryTypeDetails[0].iconName).toBe('benf-bsf-white');
component.onBeneficiaryTypeFilter('BSF');
expect(component.beneficiaryTypeDetails[0].iconName).toBe('benf-bsf');
});

it('should set iconName to activeIconName for matching type and inactive for others', () => {
component.onBeneficiaryTypeFilter('BSF');
expect(component.beneficiaryTypeDetails[0].iconName).toBe('benf-bsf-white');
expect(component.beneficiaryTypeDetails[1].iconName).toBe('benf-ksa');
});

it('should toggle activeType correctly', () => {


component.onBeneficiaryTypeFilter('BSF');
expect(component.activeType).toBe('BSF');
});

it('should change iconName based on the new beneficiaryType and reset icons if same type is
selected again', () => {
component.onBeneficiaryTypeFilter('BSF');
expect(component.beneficiaryTypeDetails[0].iconName).toBe('benf-bsf-white');
component.onBeneficiaryTypeFilter('BSF');
expect(component.beneficiaryTypeDetails[0].iconName).toBe('benf-bsf');
});
});

beneficiary-list-container.component.ts

import { HttpErrorResponse } from '@angular/common/http';


import { ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit, ViewChild } from
'@angular/core';
import { FormControl } from '@angular/forms';
import { Event, NavigationEnd, Router } from '@angular/router';
import { BeneficiaryNavigationService, BeneficiaryService } from '@bsf/beneficiary-manager-
journey-data-access';
import { Beneficiaries, BeneficiaryItem, BeneficiaryList, ItemId, View } from '@bsf/beneficiary-
manager-journey-util';
import { LANG_KEYS, LocalesService } from '@backbase/shared/util/app-core';
import { BehaviorSubject, Observable, Subject, Subscription, of } from 'rxjs';
import {
catchError,
debounceTime,
distinctUntilChanged,
filter,
map,
shareReplay,
startWith,
tap,
withLatestFrom,
} from 'rxjs/operators';
import { BeneficiaryTypeDetails } from 'libs/beneficiary-manager-journey/util/src/lib/enums';

/**
* Container component for showing beneficiary list.
*
* @usageNotes
*
* ### Display list of all the beneficiary with details / edit form.
*
* ```html
* <bb-beneficiary-list-container>
* <router-outlet></router-outlet>
* </bb-beneficiary-list-container>
* ```
* @ngModule BeneficiaryManagerJourneyFeatureModule
*/
@Component({
selector: 'bb-beneficiary-list-container',
templateUrl: './beneficiary-list-container.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BeneficiaryListContainerComponent implements OnInit, OnDestroy {
firstBeneficiaryEdit = false;
error!: HttpErrorResponse;
subscription: Subscription = new Subscription();
isSearchBoxVisible = true;
readonly searchTerm = new FormControl();
readonly debounceSearch$ = new Subject<string>();
readonly searchTerm$ = new Subject<string>();
readonly beneficiaries$ = new BehaviorSubject<Beneficiaries>([]);
listError: { shouldShow: boolean; message?: string } = {
shouldShow: false,
message: undefined,
};
beneficiariesResponse!: Beneficiaries;
beneficiaryTypeDetails = BeneficiaryTypeDetails;
currentBeneficiaryType: string = '';
activeType: string = '';
filterBeneficiaries!: Beneficiaries;
private readonly langKey = this.isEn ? LANG_KEYS.get('en-US') : LANG_KEYS.get('ar-SA');
private readonly errorMessage = $localize`:Error message@@bsf-retail.beneficiary-manager-
journey.list-container.error:Something went wrong`;

readonly isListView: Observable<boolean> = this.router.events.pipe(


filter<Event, NavigationEnd>((e: Event): e is NavigationEnd => e instanceof NavigationEnd),
map((e: NavigationEnd): string => e.url),
startWith(this.router.url),
map(() => {
const isList = this.navigation.currentView === View.List;
if (isList) {
this.firstBeneficiaryEdit = false;
}
if (this.navigation.currentView === View.Edit) {
this.firstBeneficiaryEdit = !this.isSearchBoxVisible;
}
return isList;
}),
shareReplay(),
);

readonly items: Observable<BeneficiaryList | undefined> = this.service.items.pipe(


tap((results) => {
this.isSearchBoxVisible = this.searchTerm.value ? true : results.beneficiaries?.length > 0;
results.beneficiaries.sort((a: BeneficiaryItem, b: BeneficiaryItem) =>
a.beneficiaryName.toLocaleUpperCase().trim() >
b.beneficiaryName.toLocaleUpperCase().trim() ? 1 : -1,
);
this.beneficiariesResponse = results.beneficiaries;
this.beneficiaries$.next(results.beneficiaries);
}),
catchError((error) => {
this.error = error;

const errors = error?.error?.errors;

this.listError.shouldShow = true;

if (errors?.length) {
const message = errors.find(({ key }: { key: string }) => key === this.langKey)?.message;
this.listError.message = message;
} else {
this.listError.message = this.errorMessage;
}

return of(undefined);
}),
);

/**
* Observable of currently selected item id
*/
readonly selectedItemId: Observable<ItemId | null> = this.service.currentlySelectedItemId;

@ViewChild('detailsContainer') detailsContainer?: ElementRef<HTMLDivElement>;

constructor(
private readonly service: BeneficiaryService,
private readonly navigation: BeneficiaryNavigationService,
private readonly router: Router,
private readonly localesService: LocalesService,
) {}

ngOnInit() {
this.subscription.add(
this.debounceSearch$
.pipe(debounceTime(300), distinctUntilChanged())
.subscribe((term: string) => this.searchBeneficiaries(term)),
);

this.subscription.add(
this.searchTerm$
.pipe(
withLatestFrom(this.items),
tap(([searchTerm, results]) => {
if (!results?.beneficiaries || !Array.isArray(results?.beneficiaries)) {
return this.beneficiaries$.next([]);
}
if (!searchTerm) {
return this.currentBeneficiaryType
? this.beneficiaries$.next(this.filterBeneficiaries)
: this.beneficiaries$.next(results.beneficiaries);
}
const filterBeneficiariesBySearchTerm = this.filterBeneficiariesByInput(searchTerm);

const beneficiaries = this.currentBeneficiaryType


? filterBeneficiariesBySearchTerm(this.filterBeneficiaries)
: filterBeneficiariesBySearchTerm(results.beneficiaries);

this.beneficiaries$.next(beneficiaries);
}),
)
.subscribe(),
);
}

onBeneficiaryTypeFilter(beneficiaryType: string) {
this.beneficiaryTypeDetails.forEach((value) => {
value.iconName =
value.type === beneficiaryType && beneficiaryType !== this.activeType
? value.activeIconName
: value.inActiveIconName;
});

this.activeType = this.activeType === beneficiaryType ? '' : beneficiaryType;

if (this.currentBeneficiaryType === beneficiaryType) {


this.currentBeneficiaryType = '';
this.beneficiaries$.next(this.beneficiariesResponse);
} else {
this.currentBeneficiaryType = beneficiaryType;

if (this.beneficiariesResponse) {
this.filterBeneficiaries = this.beneficiariesResponse?.filter(
(beneficiary) => beneficiary.category === beneficiaryType,
);
}

this.beneficiaries$.next(this.filterBeneficiaries);
}
}

filterBeneficiariesByInput(searchTerm) {
return (beneficiaries: Beneficiaries) =>
beneficiaries.filter((beneficiary) => {
const lowerSearchTerm = searchTerm.toLowerCase();
return (
beneficiary.maskedFullName?.toLowerCase().includes(lowerSearchTerm) ||
beneficiary.beneficiaryName?.toLowerCase().includes(lowerSearchTerm)
);
});
}
/**
* Event handler for search box value change
*
* @param {string} phrase text to search for
*/
onSearchTermChange(phrase: string) {
this.debounceSearch$.next(phrase);
}

/**
* Event handler for search box clear
*/
clearSearch() {
this.debounceSearch$.next('');
}

/**
* Queries beneficiaries with given phrase
*
* @param {string} phrase text to search for
*/
searchBeneficiaries(phrase: string) {
this.searchTerm$.next(phrase);
}

/**
* Routes to selected beneficiary details.
*
* @param {string} id Beneficiary ID
*/
select(id: string) {
this.navigation.select(id);
this.detailsContainer?.nativeElement.scrollTo({ top: 0 });
}

/**
* Routes to new beneficiary.
*
* @param {number} totalBeneficiaries - total number of beneficiary.
*/
newBeneficiary(totalBeneficiaries: number) {
this.firstBeneficiaryEdit = !totalBeneficiaries;
this.navigation.newBeneficiaryForm();
this.navigation.currentView = View.Edit;
}

/**
* method to return back to list view.
*/
backToList() {
this.navigation.currentView = View.List;
this.navigation.list();
}

ngOnDestroy() {
this.subscription?.unsubscribe();
}

onAlertClose(): void {
this.listError = {
shouldShow: false,
message: undefined,
};
}

get isEn(): boolean {


const currentLocale = this.localesService.currentLocale;
const en = 'en';

return currentLocale.includes(en);
}
}
src/libs/bsf-p2p-initiate-payment-journey/src/lib/services/bsf-p2p-initiate-
payment.service.ts

activeBeneficiaryType: string;

src/themes/scss/1-variables/components/_icon-custom.scss

//beneficiary-types
'bsf-benf-bsf-icon': url('#{$path-bsf-images}/benf-bsf.svg'),
'bsf-benf-ksa-icon': url('#{$path-bsf-images}/benf-ksa.svg'),
'bsf-benf-int-icon': url('#{$path-bsf-images}/benf-int.svg'),
'bsf-benf-wu-icon': url('#{$path-bsf-images}/benf-wu.svg'),
'bsf-benf-company-icon': url('#{$path-bsf-images}/benf-company.svg'),
'bsf-benf-brokerage-icon': url('#{$path-bsf-images}/benf-brokerage.svg'),
'bsf-benf-bsf-white-icon': url('#{$path-bsf-images}/benf-bsf-white.svg'),
'bsf-benf-ksa-white-icon': url('#{$path-bsf-images}/benf-ksa-white.svg'),
'bsf-benf-int-white-icon': url('#{$path-bsf-images}/benf-int-white.svg'),
'bsf-benf-wu-white-icon': url('#{$path-bsf-images}/benf-wu-white.svg'),
'bsf-benf-company-white-icon': url('#{$path-bsf-images}/benf-company-white.svg'),
'bsf-benf-brokerage-white-icon': url('#{$path-bsf-images}/benf-brokerage-white.svg'),

src/themes/scss/5-components/forms/_bb-custom-payment-journey.scss

.beneficiary-text-support {
color: $color-secondary-darkest;
}
.custom-beneficiary-type {
margin-top: $spacer-sm;
.custom-beneficiary-type-btn,
button.custom-beneficiary-type-btn.inactive {
display: inline-flex;
border: 1px solid $color-secondary;
border-radius: $border-radius;
padding: $sizer-sm;
align-items: center;
margin-right: $spacer-sm;
background-color: $color-neutral-white;
color: $color-secondary;
&:last-child {
margin-top: $spacer-sm;
}
}
.custom-beneficiary-type-btn {
&.active,
&:focus {
background-color: $color-secondary;
color: $color-neutral-white;
}
}
}

src/themes/scss/5-components/_bb-beneficiaries.scss

.beneficiary-text-support {
color: $color-secondary-darkest;
}
.custom-beneficiary-type {
margin-top: $spacer-sm;
.custom-beneficiary-type-btn,
button.custom-beneficiary-type-btn.inactive {
display: inline-flex;
border: 1px solid $color-secondary;
border-radius: $border-radius;
padding: $sizer-sm;
align-items: center;
margin-right: $spacer-sm;
background-color: $color-neutral-white;
color: $color-secondary;
&:last-child {
margin-top: $spacer-sm;
}
}
.custom-beneficiary-type-btn {
&.active,
&:focus {
background-color: $color-secondary;
color: $color-neutral-white;
}
}
}

You might also like