Skip to content

Commit

Permalink
test: test for opt in and connect card (#3406)
Browse files Browse the repository at this point in the history
  • Loading branch information
bistaastha authored Jan 8, 2025
1 parent 89022d3 commit 8f4abaf
Show file tree
Hide file tree
Showing 6 changed files with 660 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
This will help you bring your card transactions into Fyle as expenses instantly.
</div>
<form [formGroup]="fg">
<div *ngIf="enrollableCards.length > 0">
<div *ngIf="enrollableCards.length > 0" class="connect-card__multiple-cards">
<div *ngFor="let card of enrollableCards; let i = index">
<div class="connect-card__input-label">
<span>Corporate card</span>
Expand All @@ -27,7 +27,7 @@
data-testid="card-number-input"
formControlName="card_number_{{ card.id }}"
required
(input)="onCardNumberUpdate(card, 'card_number_' + card.id)"
(input)="onCardNumberUpdate(card)"
/>

<div class="connect-card__card-last-four">
Expand Down Expand Up @@ -68,6 +68,10 @@
>Please enter a valid card number.</span
>

<span *ngIf="fg.controls['card_number_' + card.id]?.errors.enrollmentError">{{
cardValuesMap[card.id].enrollment_error
}}</span>

<ng-container *ngIf="fg.controls['card_number_' + card.id]?.errors.invalidCardNetwork">
<span *ngIf="isVisaRTFEnabled && isMastercardRTFEnabled; else visaOnlyOrg"
>Enter a valid Visa or Mastercard number. If you have other cards, please contact your admin.</span
Expand Down Expand Up @@ -148,6 +152,9 @@
data-testid="error-message"
>
<span *ngIf="fg.controls.card_number.errors.invalidCardNumber">Please enter a valid card number.</span>
<span *ngIf="fg.controls.card_number.errors.enrollmentError">
{{ singularEnrollmentFailure }}
</span>

<ng-container *ngIf="fg.controls.card_number?.errors.invalidCardNetwork">
<span *ngIf="isVisaRTFEnabled && isMastercardRTFEnabled; else visaOnlyOrg"
Expand Down Expand Up @@ -177,9 +184,11 @@
<ion-button
class="btn-primary connect-card__primary-cta"
fill="clear"
[disabled]="fg.invalid"
role="button"
(click)="enrollCards()"
appFormButtonValidation
[loading]="cardsEnrolling"
[loadingText]="'Continue'"
>
Continue
</ion-button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
position: relative;
height: 100%;

&__multiple-cards {
max-height: 200px;
overflow-y: scroll;
display: flex;
flex-direction: column;
gap: 20px;
}

&__body {
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -76,8 +84,9 @@
}

&__primary-cta {
width: 108px;
width: 50%;
align-self: flex-end;
margin-bottom: 20px;
}

&__toolbar-title {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import {
AbstractControl,
FormArray,
FormBuilder,
FormControl,
FormGroup,
ValidationErrors,
Validators,
} from '@angular/forms';
ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
} from '@angular/core';
import { AbstractControl, FormBuilder, FormControl, FormGroup, ValidationErrors, Validators } from '@angular/forms';
import { PopoverController } from '@ionic/angular';
import { catchError, concatMap, from, map, noop, of, switchMap, tap } from 'rxjs';
import { CardNetworkType } from 'src/app/core/enums/card-network-type';
Expand Down Expand Up @@ -41,7 +43,7 @@ export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChan

enrollableCards: PlatformCorporateCard[] = [];

cardValuesMap: Record<string, { card_type: string; last_four: string }> = {};
cardValuesMap: Record<string, { card_type: string; last_four: string; enrollment_error?: string }> = {};

rtfCardType: CardNetworkType;

Expand All @@ -59,43 +61,78 @@ export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChan

fg: FormGroup;

cardsEnrolling = false;

singularEnrollmentFailure: string;

constructor(
private corporateCreditCardExpensesService: CorporateCreditCardExpenseService,
private realTimeFeedService: RealTimeFeedService,
private fb: FormBuilder,
private popoverController: PopoverController
) {}

setupErrorMessages(error: HttpErrorResponse, cardNumber: string, cardId?: string): void {
this.cardsList.failedCards.push(`**** ${cardNumber}`);
this.handleEnrollmentFailures(error, cardId);
}

enrollMultipleCards(cards: PlatformCorporateCard[]): void {
from(cards)
.pipe(
concatMap((card) =>
this.realTimeFeedService
.enroll(this.fg.controls[`card_number_${card.id}`].value + this.cardValuesMap[card.id].last_four, card.id)
.pipe(
map(() => {
this.cardsList.successfulCards.push(`**** ${card.card_number.slice(-4)}`);
}),
catchError((error: HttpErrorResponse) => {
this.setupErrorMessages(error, this.fg.controls[`card_number_${card.id}`].value as string, card.id);
return of(error);
})
)
)
)
.subscribe(() => {
this.cardsEnrolling = false;
if (this.cardsList.failedCards.length > 0) {
this.showErrorPopover();
} else {
this.isStepComplete.emit(true);
}
});
}

enrollSingularCard(): void {
this.realTimeFeedService
.enroll(this.fg.controls.card_number.value as string)
.pipe(
map(() => {
this.cardsList.successfulCards.push(`**** ${(this.fg.controls.card_number.value as string).slice(-4)}`);
}),
catchError((error: HttpErrorResponse) => {
this.setupErrorMessages(error, this.fg.controls.card_number.value as string);
return of(error);
})
)
.subscribe(() => {
this.cardsEnrolling = false;
if (this.cardsList.failedCards.length > 0) {
this.showErrorPopover();
} else {
this.isStepComplete.emit(true);
}
});
}

enrollCards(): void {
const cards = this.enrollableCards;
this.cardsEnrolling = true;
if (cards.length > 0) {
from(cards)
.pipe(
concatMap((card) =>
this.realTimeFeedService
.enroll(this.fg.controls[`card_number_${card.id}`].value + this.cardValuesMap[card.id].last_four, card.id)
.pipe(
map(() => {
this.cardsList.successfulCards.push(`**** ${card.card_number.slice(-4)}`);
}),
catchError(() => {
this.cardsList.failedCards.push(`**** ${card.card_number.slice(-4)}`);
return of(null);
})
)
)
)
.subscribe(() => {
if (this.cardsList.failedCards.length > 0) {
this.showErrorPopover();
} else {
this.isStepComplete.emit(true);
}
});
this.enrollMultipleCards(cards);
} else {
this.realTimeFeedService.enroll(this.fg.controls.card_number.value as string).subscribe(() => {
this.isStepComplete.emit(true);
});
this.enrollSingularCard();
}
}

Expand All @@ -104,13 +141,13 @@ export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChan
return 'We ran into an issue while processing your request. You can cancel and retry connecting the failed card or proceed to the next step.';
} else if (this.cardsList.failedCards.length > 0) {
return `
We ran into an issue while processing your request for the card ${this.cardsList.failedCards[0]}.
We ran into an issue while processing your request for the cards ${this.cardsList.failedCards
.slice(0, this.cardsList.failedCards.length - 1)
.join(', ')} and ${this.cardsList.failedCards.slice(-1)}.
You can cancel and retry connecting the failed card or proceed to the next step.`;
} else {
return `
We ran into an issue while processing your request for the cards ${this.cardsList.failedCards
.slice(this.cardsList.failedCards.length - 1)
.join(', ')} and ${this.cardsList.failedCards.slice(-1)}.
We ran into an issue while processing your request for the card ${this.cardsList.failedCards[0]}.
You can cancel and retry connecting the failed card or proceed to the next step.`;
}
}
Expand Down Expand Up @@ -174,8 +211,8 @@ export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChan
new FormControl('', [
Validators.required,
Validators.maxLength(12),
this.cardNumberValidator.bind(this),
this.cardNetworkValidator.bind(this),
this.cardNumberValidator,
this.cardNetworkValidator,
])
);
});
Expand Down Expand Up @@ -217,12 +254,23 @@ export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChan
}
}

private handleEnrollmentFailures(error: Error, cardId: string): void {
const enrollmentFailureMessage = error.message || 'Something went wrong. Please try after some time.';
if (this.enrollableCards.length > 0) {
this.fg.controls[`card_number_${cardId}`].setErrors({ enrollmentError: true });
this.cardValuesMap[cardId].enrollment_error = enrollmentFailureMessage;
} else {
this.fg.controls.card_number.setErrors({ enrollmentError: true });
this.singularEnrollmentFailure = enrollmentFailureMessage;
}
}

private cardNumberValidator(control: AbstractControl): ValidationErrors {
// Reactive forms are not strongly typed in Angular 13, so we need to cast the value to string
// TODO (Angular 14 >): Remove the type casting and directly use string type for the form control
const cardNumber = control.value as string;

const isValid = this.realTimeFeedService.isCardNumberValid(cardNumber.replace(/ /g, ''));
const isValid = this.realTimeFeedService.isCardNumberValid(cardNumber);
const cardType = this.realTimeFeedService.getCardTypeFromNumber(cardNumber);

if (cardType === CardNetworkType.VISA || cardType === CardNetworkType.MASTERCARD) {
Expand Down
Loading

0 comments on commit 8f4abaf

Please sign in to comment.