From 8e62320abd65a540c2efc56234f1bbe8492e998c Mon Sep 17 00:00:00 2001 From: aastha Date: Thu, 26 Dec 2024 14:59:59 +0530 Subject: [PATCH] Minor --- ...nboarding-connect-card-step.component.html | 140 ++++++++------- ...nboarding-connect-card-step.component.scss | 8 + ...-onboarding-connect-card-step.component.ts | 167 +++++++++++++++++- 3 files changed, 248 insertions(+), 67 deletions(-) diff --git a/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.html b/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.html index 229c2ff95a..8c31a5e04b 100644 --- a/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.html +++ b/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.html @@ -5,77 +5,94 @@ This will help you bring your card transactions into Fyle as expenses instantly. -
- Corporate card -
- -
- +
+
+
+
+ Corporate card +
- +
+ - +
+ {{ card?.card_number || '' }} +
- -
+ -
+ -
- 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. - + Please enter a valid card number. - - - Enter a valid Mastercard number. If you have other cards, please contact your admin. - -
+ + Enter a valid Visa or Mastercard number. If you have other cards, please contact your admin. - - {{ enrollmentFailureMessage }} - -
+ + + 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. + + +
+
+ +
+
Continue 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 035f11119a..360329bd83 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 @@ -17,6 +17,14 @@ justify-content: flex-end; } + &__card-number-input { + width: fit-content !important; + margin-right: 24px; + &::placeholder { + word-spacing: 24px; + } + } + &__heading { color: $black; font-size: 20px; 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 b3eb0c429d..b38db0c174 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,16 +1,31 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { AbstractControl, FormControl, ValidationErrors } from '@angular/forms'; +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { + AbstractControl, + FormArray, + FormBuilder, + FormControl, + FormGroup, + ValidationErrors, + Validators, +} from '@angular/forms'; +import { PopoverController } from '@ionic/angular'; +import { catchError, concatMap, finalize, 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'; +import { PopoverCardsList } from 'src/app/core/models/popover-cards-list.model'; import { CorporateCreditCardExpenseService } from 'src/app/core/services/corporate-credit-card-expense.service'; import { RealTimeFeedService } from 'src/app/core/services/real-time-feed.service'; +import { PopupAlertComponent } from 'src/app/shared/components/popup-alert/popup-alert.component'; @Component({ selector: 'app-spender-onboarding-connect-card-step', templateUrl: './spender-onboarding-connect-card-step.component.html', styleUrls: ['./spender-onboarding-connect-card-step.component.scss'], }) -export class SpenderOnboardingConnectCardStepComponent { +export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChanges { @Input() readOnly?: boolean = false; @Input() orgSettings: OrgSettings; @@ -25,13 +40,153 @@ export class SpenderOnboardingConnectCardStepComponent { cardType = CardNetworkType; + enrollableCards: PlatformCorporateCard[]; + + cardValuesMap: Record = {}; + + rtfCardType: CardNetworkType; + + cardsList: PopoverCardsList = { + successfulCards: [], + failedCards: [], + }; + + fg: FormGroup; + constructor( private corporateCreditCardExpensesService: CorporateCreditCardExpenseService, - private realTimeFeedService: RealTimeFeedService + private realTimeFeedService: RealTimeFeedService, + private fb: FormBuilder, + private popoverController: PopoverController ) {} - ionViewWillEnter(): void { - this.cardForm = new FormControl('', [this.cardNumberValidator.bind(this), this.cardNetworkValidator.bind(this)]); + enrollCards(): void { + const cards = this.enrollableCards; + 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); + }) + ) + ) + ) + .subscribe(() => { + if (this.cardsList.failedCards.length > 0) { + this.showErrorPopover(); + } else { + this.isStepCompleted.emit(true); + } + }); + } + + 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) { + 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 + .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.`; + } + } + + showErrorPopover(): void { + const errorPopover = this.popoverController.create({ + componentProps: { + title: 'Status summary', + message: this.generateMessage(), + primaryCta: { + text: 'Proceed anyway', + action: 'close', + }, + secondaryCta: { + text: 'Cancel', + action: 'cancel', + }, + cardsList: this.cardsList.successfulCards.length > 0 ? this.cardsList : {}, + }, + component: PopupAlertComponent, + cssClass: 'pop-up-in-center', + }); + + from(errorPopover) + .pipe( + tap((errorPopover) => errorPopover.present()), + switchMap((errorPopover) => errorPopover.onWillDismiss()), + map((response: OverlayResponse<{ action?: string }>) => { + if (response?.data?.action === 'close') { + this.isStepCompleted.emit(true); + } + }) + ) + .subscribe(noop); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.orgSettings.currentValue) { + this.isVisaRTFEnabled = this.orgSettings.visa_enrollment_settings.enabled; + this.isMastercardRTFEnabled = this.orgSettings.mastercard_enrollment_settings.enabled; + } + } + + ngOnInit(): void { + this.fg = this.fb.group({}); + 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, + card_type: CardNetworkType.OTHERS, + }; + this.fg.addControl( + controlName, + this.fb.control('', [ + Validators.required, + Validators.maxLength(12), + this.cardNumberValidator.bind(this), + this.cardNetworkValidator.bind(this), + ]) + ); + }); + }) + ) + .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 + ); + } + + 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); } private cardNumberValidator(control: AbstractControl): ValidationErrors {