0">
-
+
+
+ Corporate card
+
+
+
+
+
+
+
+ Please enter a valid card number.
+
+
+ Enter a valid Visa or Mastercard number. If you have other cards, please contact your admin.
+
+
+
+ Enter a valid Visa number. If you have other cards, please contact your admin.
+
+
+
+
+ Enter a valid Mastercard number. If you have other cards, please contact your admin.
+
+
+
+
diff --git a/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.scss b/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.scss
index 360329bd83..b20f990794 100644
--- a/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.scss
+++ b/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.scss
@@ -18,13 +18,36 @@
}
&__card-number-input {
- width: fit-content !important;
margin-right: 24px;
+ font-size: 18px;
+ font-weight: 500;
&::placeholder {
+ font-size: 14px;
+ font-weight: 400;
word-spacing: 24px;
}
}
+ &__card-number-input-singular {
+ border: 0;
+ font-size: 18px;
+ font-weight: 500;
+ &::placeholder {
+ font-size: 14px;
+ font-weight: 400;
+ }
+ }
+
+ &__card-number-input-container {
+ display: flex;
+ justify-content: space-between;
+ }
+
+ &__card-last-four {
+ font-size: 18px;
+ font-weight: 500;
+ }
+
&__heading {
color: $black;
font-size: 20px;
@@ -91,6 +114,7 @@
&__input-inner-container {
display: flex;
align-items: center;
+ justify-content: space-between;
border-bottom: 1px solid $grey;
padding-bottom: 6px;
margin-bottom: 2px;
diff --git a/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts b/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts
index 60b64498a9..dc8d799401 100644
--- a/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts
+++ b/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts
@@ -1,4 +1,13 @@
-import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
+import {
+ ChangeDetectorRef,
+ Component,
+ EventEmitter,
+ Input,
+ OnChanges,
+ OnInit,
+ Output,
+ SimpleChanges,
+} from '@angular/core';
import {
AbstractControl,
FormArray,
@@ -9,9 +18,8 @@ import {
Validators,
} from '@angular/forms';
import { PopoverController } from '@ionic/angular';
-import { catchError, concatMap, finalize, from, map, noop, of, switchMap, tap } from 'rxjs';
+import { catchError, concatMap, from, map, noop, of, switchMap, tap } from 'rxjs';
import { CardNetworkType } from 'src/app/core/enums/card-network-type';
-import { statementUploadedCard, visaRTFCard } from 'src/app/core/mock-data/platform-corporate-card.data';
import { OrgSettings } from 'src/app/core/models/org-settings.model';
import { OverlayResponse } from 'src/app/core/models/overlay-response.modal';
import { PlatformCorporateCard } from 'src/app/core/models/platform/platform-corporate-card.model';
@@ -25,7 +33,6 @@ import { PopupAlertComponent } from 'src/app/shared/components/popup-alert/popup
templateUrl: './spender-onboarding-connect-card-step.component.html',
styleUrls: ['./spender-onboarding-connect-card-step.component.scss'],
})
-
export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChanges {
@Input() readOnly?: boolean = false;
@@ -41,12 +48,19 @@ export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChan
cardType = CardNetworkType;
- enrollableCards: PlatformCorporateCard[];
+ enrollableCards: PlatformCorporateCard[] = [];
- cardValuesMap: Record = {};
+ cardValuesMap: Record = {};
rtfCardType: CardNetworkType;
+ cardsLoading = true;
+
+ singleEnrollableCardDetails: { card_type: string; card_number: string } = {
+ card_type: '',
+ card_number: '',
+ };
+
cardsList: PopoverCardsList = {
successfulCards: [],
failedCards: [],
@@ -66,15 +80,17 @@ export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChan
from(cards)
.pipe(
concatMap((card) =>
- this.realTimeFeedService.enroll(card.card_number, 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);
- })
- )
+ 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(() => {
@@ -89,13 +105,13 @@ export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChan
generateMessage(): string {
if (this.cardsList.successfulCards.length > 0) {
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.successfulCards.length > 0) {
+ } else if (this.cardsList.failedCards.length > 0) {
return `
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.`;
} else {
return `
- We ran into an issue while processing your request for the card ${this.cardsList.failedCards
+ 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)}.
You can cancel and retry connecting the failed card or proceed to the next step.`;
@@ -105,7 +121,7 @@ export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChan
showErrorPopover(): void {
const errorPopover = this.popoverController.create({
componentProps: {
- title: 'Status summary',
+ title: this.cardsList.successfulCards.length > 0 ? 'Status summary' : 'Failed connecting',
message: this.generateMessage(),
primaryCta: {
text: 'Proceed anyway',
@@ -141,60 +157,75 @@ export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChan
}
}
- ngOnInit(): void {
- this.fg = this.fb.group({});
+ setupForm(): void {
+ this.cardsLoading = true;
this.corporateCreditCardExpensesService
.getCorporateCards()
.pipe(
map((corporateCards) => {
- // Filter enrollable cards
this.enrollableCards = corporateCards.filter((card) => card.data_feed_source === 'STATEMENT_UPLOAD');
- // Add form controls for each enrollable card
- this.enrollableCards.forEach((card, index) => {
- const controlName = `card_number_${index}`;
- this.cardValuesMap[card.id] = {
- card_number: card.card_number,
+ if (this.enrollableCards.length > 0) {
+ this.enrollableCards.forEach((card) => {
+ const controlName = `card_number_${card.id}`;
+ this.cardValuesMap[card.id] = {
+ last_four: card.card_number.slice(-4),
+ card_type: CardNetworkType.OTHERS,
+ };
+ this.fg.addControl(
+ controlName,
+ new FormControl('', [
+ Validators.required,
+ Validators.maxLength(12),
+ this.cardNumberValidator.bind(this),
+ this.cardNetworkValidator.bind(this),
+ ])
+ );
+ });
+ } else {
+ this.singleEnrollableCardDetails = {
+ card_number: null,
card_type: CardNetworkType.OTHERS,
};
this.fg.addControl(
- controlName,
- this.fb.control('', [
+ 'card_number',
+ new FormControl('', [
Validators.required,
- Validators.maxLength(12),
+ Validators.maxLength(16),
this.cardNumberValidator.bind(this),
this.cardNetworkValidator.bind(this),
])
);
- });
+ }
+ this.cardsLoading = false;
})
)
.subscribe();
}
- onCardNumberUpdate(card: PlatformCorporateCard, inputControlName: string): void {
- this.formatCardNumber(this.fg.controls[inputControlName]);
- this.cardValuesMap[card.id].card_type = this.realTimeFeedService.getCardTypeFromNumber(
- this.cardValuesMap[card.id].card_number
- );
+ ngOnInit(): void {
+ this.fg = this.fb.group({});
+ this.setupForm();
}
- formatCardNumber(input: AbstractControl): void {
- // Remove all non-numeric characters
- let value = (input.value as string).replace(/\D/g, '');
-
- // Format the value in groups of 4
- value = value.replace(/(\d{4})(?=\d)/g, '$1 ');
-
- // Set the formatted value back to the input
- input.setValue(value);
+ onCardNumberUpdate(card?: PlatformCorporateCard): void {
+ if (this.enrollableCards.length > 0) {
+ this.cardValuesMap[card.id].card_type = this.realTimeFeedService.getCardTypeFromNumber(
+ this.fg.controls[`card_number_${card.id}`].value as string
+ );
+ } else {
+ this.singleEnrollableCardDetails.card_type = this.realTimeFeedService.getCardTypeFromNumber(
+ this.fg.controls.card_number.value as string
+ );
+ }
+ }
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);
+ const isValid = this.realTimeFeedService.isCardNumberValid(cardNumber.replace(/ /g, ''));
const cardType = this.realTimeFeedService.getCardTypeFromNumber(cardNumber);
if (cardType === CardNetworkType.VISA || cardType === CardNetworkType.MASTERCARD) {
diff --git a/src/app/fyle/spender-onboarding/spender-onboarding.module.ts b/src/app/fyle/spender-onboarding/spender-onboarding.module.ts
index f5a3827051..888dfe0ec8 100644
--- a/src/app/fyle/spender-onboarding/spender-onboarding.module.ts
+++ b/src/app/fyle/spender-onboarding/spender-onboarding.module.ts
@@ -9,6 +9,7 @@ import { MatButtonModule } from '@angular/material/button';
import { SpenderOnboardingConnectCardStepComponent } from './spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component';
import { SpenderOnboardingOptInStepComponent } from './spender-onboarding-opt-in-step/spender-onboarding-opt-in-step.component';
import { NgOtpInputModule } from 'ng-otp-input';
+import { NgxMaskModule } from 'ngx-mask';
@NgModule({
imports: [
@@ -21,6 +22,9 @@ import { NgOtpInputModule } from 'ng-otp-input';
FormsModule,
ReactiveFormsModule,
NgOtpInputModule,
+ NgxMaskModule.forRoot({
+ validation: false,
+ }),
],
declarations: [SpenderOnboardingPage, SpenderOnboardingConnectCardStepComponent, SpenderOnboardingOptInStepComponent],
})
diff --git a/src/app/fyle/spender-onboarding/spender-onboarding.page.html b/src/app/fyle/spender-onboarding/spender-onboarding.page.html
index 62b6f32359..5807b7e12d 100644
--- a/src/app/fyle/spender-onboarding/spender-onboarding.page.html
+++ b/src/app/fyle/spender-onboarding/spender-onboarding.page.html
@@ -21,9 +21,10 @@
class="spender-onboarding__progress-bar spender-onboarding__progress-bar-right"
[src]="'/assets/svg/progress-bar.svg'"
slot="icon-only"
+ [ngClass]="{'spender-onboarding__step-next': currentStep === 'CONNECT_CARD', 'spender-onboarding__step-hide': onlyOptInEnabled}"
>
-
Skip
+
Skip