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 new file mode 100644 index 0000000000..229c2ff95a --- /dev/null +++ b/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.html @@ -0,0 +1,90 @@ +
+
+
Connect corporate card
+
+ This will help you bring your card transactions into Fyle as expenses instantly. +
+ +
+ 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. + + + + + {{ enrollmentFailureMessage }} + +
+
+
+ + 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 new file mode 100644 index 0000000000..035f11119a --- /dev/null +++ b/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.scss @@ -0,0 +1,188 @@ +@import '../../../../theme/colors.scss'; + +.connect-card { + position: relative; + height: 100%; + + &__body { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + } + + &__primary-cta-container { + display: flex; + flex-direction: row; + justify-content: flex-end; + } + + &__heading { + color: $black; + font-size: 20px; + font-weight: 500; + line-height: normal; + margin-bottom: 8px; + margin-top: 32px; + } + + &__sub-heading { + color: $dark-grey; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 1.28; + margin-bottom: 32px; + } + + &__primary-cta { + width: 108px; + align-self: flex-end; + } + + &__toolbar-title { + font-size: 20px; + font-weight: 500; + color: $black; + line-height: normal; + } + + &__toolbar-close-btn { + position: absolute; + } + + &__body { + display: flex; + flex-direction: column; + } + + &__content { + --padding-start: 16px; + --padding-end: 16px; + --padding-top: 16px; + + max-height: 50vh; + } + + &__input-container { + padding: 16px 16px 8px 16px; + border-radius: 8px; + border: 1px solid $grey; + + &:focus-within:not(&--error) { + border: 1px solid $black-light; + } + } + + &__input-label { + font-size: 12px; + color: $black-light; + margin-bottom: 6px; + } + + &__input-inner-container { + display: flex; + align-items: center; + border-bottom: 1px solid $grey; + padding-bottom: 6px; + margin-bottom: 2px; + + &--error { + border-bottom: 1px solid $red; + } + + &:focus-within:not(&--error) { + border-bottom: 1px solid $black-light; + } + } + + &__card-number-input { + border: 0; + color: $blue-black; + width: 100%; + } + + &__input-default-icon { + width: 20px; + height: 20px; + color: $grey-light; + } + + &__input-visa-icon, + &__input-mastercard-icon { + width: 38px; + height: 22px; + } + + &__input-error-space { + width: 0px; + height: 16px; + float: right; + } + + &__input-errors { + color: $red; + font-size: 12px; + line-height: 1.3; + + & > :not(:first-child) { + // Only show one error message at a time + display: none; + } + } + + &__view-tnc-btn { + width: 100%; + background: $pink-gradient; + background-clip: text; + color: transparent; + padding: 12px 0; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + font-size: 14px; + } + + &__view-tnc-btn-icon { + width: 18px; + height: 18px; + color: $brand-primary; + } + + &__tnc { + display: flex; + flex-direction: column; + gap: 24px; + padding-top: 8px; + font-size: 14px; + } + + &__tnc-heading { + font-weight: 500; + color: $black; + } + + &__tnc-list { + margin: 0; + padding-left: 20px; + display: flex; + flex-direction: column; + gap: 16px; + } + + &__tnc-link { + color: $blue-4; + text-decoration: none; + } + + &__tnc-link-icon { + width: 16px; + height: 16px; + vertical-align: text-bottom; + } + + &__footer-toolbar { + padding: 16px; + } +} diff --git a/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.spec.ts b/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..b3eb0c429d --- /dev/null +++ b/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts @@ -0,0 +1,65 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { AbstractControl, FormControl, ValidationErrors } from '@angular/forms'; +import { CardNetworkType } from 'src/app/core/enums/card-network-type'; +import { OrgSettings } from 'src/app/core/models/org-settings.model'; +import { CorporateCreditCardExpenseService } from 'src/app/core/services/corporate-credit-card-expense.service'; +import { RealTimeFeedService } from 'src/app/core/services/real-time-feed.service'; + +@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 { + @Input() readOnly?: boolean = false; + + @Input() orgSettings: OrgSettings; + + @Output() isStepCompleted: EventEmitter = new EventEmitter(); + + cardForm: FormControl; + + isVisaRTFEnabled = false; + + isMastercardRTFEnabled = false; + + cardType = CardNetworkType; + + constructor( + private corporateCreditCardExpensesService: CorporateCreditCardExpenseService, + private realTimeFeedService: RealTimeFeedService + ) {} + + ionViewWillEnter(): void { + this.cardForm = new FormControl('', [this.cardNumberValidator.bind(this), this.cardNetworkValidator.bind(this)]); + } + + 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 cardType = this.realTimeFeedService.getCardTypeFromNumber(cardNumber); + + if (cardType === CardNetworkType.VISA || cardType === CardNetworkType.MASTERCARD) { + return isValid && cardNumber.length === 16 ? null : { invalidCardNumber: true }; + } + + return isValid ? null : { invalidCardNumber: true }; + } + + private cardNetworkValidator(control: AbstractControl): ValidationErrors { + const cardNumber = control.value as string; + const cardType = this.realTimeFeedService.getCardTypeFromNumber(cardNumber); + + if ( + (!this.isVisaRTFEnabled && cardType === CardNetworkType.VISA) || + (!this.isMastercardRTFEnabled && cardType === CardNetworkType.MASTERCARD) + ) { + return { invalidCardNetwork: true }; + } + + return null; + } +} diff --git a/src/app/fyle/spender-onboarding/spender-onboarding.page.scss b/src/app/fyle/spender-onboarding/spender-onboarding.page.scss index c9019abffd..c79f3c07dc 100644 --- a/src/app/fyle/spender-onboarding/spender-onboarding.page.scss +++ b/src/app/fyle/spender-onboarding/spender-onboarding.page.scss @@ -20,6 +20,10 @@ $toolbar-border: #ababab6b; padding: 20px 16px; } + &__component-container { + height: 75%; + } + &__step-tracker { padding: 24px; border-radius: 32px 32px 0px 0px;