Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implementation for connect cards part 2 #3389

Open
wants to merge 13 commits into
base: FYLE-86cx2t82k-base-feature-branch
Choose a base branch
from

Conversation

bistaastha
Copy link
Contributor

@bistaastha bistaastha commented Dec 26, 2024

Changes in the PR:

  • Adds dynamic inputs for multiple cards
  • Adds dialog for error

Fixes to be added in follow up:

  • Fix input CSS and validation
  • test singular input functionality
  • Controller tests

Preview:
Screenshot 2024-12-26 at 11 34 00 AM
Screenshot 2024-12-26 at 3 05 00 PM

Summary by CodeRabbit

  • New Features

    • Introduced a new component for connecting corporate cards during the spender onboarding process, featuring dynamic form inputs and validation.
    • Added a detailed feedback section in the popup alert component to display successful and failed card connections.
    • Implemented a progress tracker and user greeting in the onboarding page.
  • Bug Fixes

    • Enhanced error handling and user feedback for card number validation.
  • Documentation

    • Updated component structure and functionality descriptions for improved user guidance.

Copy link

coderabbitai bot commented Dec 26, 2024

Walkthrough

Listen up, superstar! 🌟 We've rolled out a brand new component for spender onboarding that connects corporate cards with flair! This change introduces a dynamic HTML form that allows users to enroll multiple cards, complete with validation and error handling. The user interface is designed to be smooth and responsive, ensuring a seamless onboarding experience that makes corporate card enrollment feel like a blockbuster hit!

Changes

File Change Summary
src/app/fyle/spender-onboarding/...connect-card-step.component.html New HTML component for card connection with dynamic form and validation
src/app/fyle/spender-onboarding/...connect-card-step.component.scss Styling for the card connection step with responsive design
src/app/fyle/spender-onboarding/...connect-card-step.component.ts Enhanced component logic with form management, card enrollment, and error handling
src/app/fyle/spender-onboarding/spender-onboarding.page.* Updated onboarding page to include new card connection step
src/app/shared/components/popup-alert/* Added support for displaying card connection results

Possibly related PRs

Suggested Reviewers

  • arjunaj5
  • Chethan-Fyle
  • Dimple16

Poem

Behold, the card connection dance! 💳
Where forms flow like a hero's stance
Enrolling cards with magical might
Validation sparkling, oh so bright
Onboarding magic, Rajini's way! 🌟

Tip

CodeRabbit's docstrings feature is now available as part of our Early Access Program! Simply use the command @coderabbitai generate docstrings to have CodeRabbit automatically generate docstrings for your pull request. We would love to hear your feedback on Discord.


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@bistaastha bistaastha changed the title Fyle 86cx2t82k implementation connect cards 2 feat: Implementation for connect cards part 2 Dec 26, 2024
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c53949 and 648f6c0.

⛔ Files ignored due to path filters (2)
  • src/assets/svg/check.svg is excluded by !**/*.svg
  • src/assets/svg/progress-bar.svg is excluded by !**/*.svg
📒 Files selected for processing (17)
  • src/app/core/mock-data/onboarding-status.data.ts (1 hunks)
  • src/app/core/services/spender-onboarding.service.spec.ts (1 hunks)
  • src/app/core/services/spender-onboarding.service.ts (1 hunks)
  • src/app/fyle/fyle-routing.module.ts (1 hunks)
  • src/app/fyle/spender-onboarding/models/onboarding-step.enum.ts (1 hunks)
  • src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.html (1 hunks)
  • src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.scss (1 hunks)
  • src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts (1 hunks)
  • src/app/fyle/spender-onboarding/spender-onboarding-routing.module.ts (1 hunks)
  • src/app/fyle/spender-onboarding/spender-onboarding.module.ts (1 hunks)
  • src/app/fyle/spender-onboarding/spender-onboarding.page.html (1 hunks)
  • src/app/fyle/spender-onboarding/spender-onboarding.page.scss (1 hunks)
  • src/app/fyle/spender-onboarding/spender-onboarding.page.spec.ts (1 hunks)
  • src/app/fyle/spender-onboarding/spender-onboarding.page.ts (1 hunks)
  • src/app/shared/components/popup-alert/popup-alert.component.html (1 hunks)
  • src/app/shared/components/popup-alert/popup-alert.component.scss (1 hunks)
  • src/app/shared/components/popup-alert/popup-alert.component.ts (2 hunks)
🧰 Additional context used
🪛 eslint
src/app/shared/components/popup-alert/popup-alert.component.ts

[error] 3-3: 'CardNetworkType' is defined but never used.

(@typescript-eslint/no-unused-vars)

src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts

[error] 4-4: 'FormArray' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 12-12: 'finalize' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 14-14: 'statementUploadedCard' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 14-14: 'visaRTFCard' is defined but never used.

(@typescript-eslint/no-unused-vars)

src/app/fyle/spender-onboarding/spender-onboarding.page.ts

[error] 3-3: 'ExtendedOrgUser' is defined but never used.

(@typescript-eslint/no-unused-vars)

🔇 Additional comments (45)
src/app/fyle/spender-onboarding/models/onboarding-step.enum.ts (1)

1-4: "Thalaiva, your OnboardingStep Enum is sleek!"
Super job defining these steps, boss. No obstacles spotted!

src/app/fyle/spender-onboarding/spender-onboarding-routing.module.ts (1)

1-16: "Route-a set panna, perfect-u da!"
This routing module is crisp and does the job well. Everything is neat and clean, and no refactor needed now.

src/app/core/mock-data/onboarding-status.data.ts (1)

1-14: "Data freeze-u, no messing around!"
Your onboardingStatusData is well-structured. Just ensure any future additions align with OnboardingState.

src/app/shared/components/popup-alert/popup-alert.component.ts (3)

4-4: "PopoverCardsList import: super cool"
This import for PopoverCardsList sets the stage for interesting card displays, boss!


21-21: "New property, new possibilities!"
cardsList input is a power move for dynamic updates. Great job!


25-25: "Function signature, locked and loaded!"
Explicit parameter and return type keep things nice and safe. Smooth ride ahead!

src/app/core/services/spender-onboarding.service.ts (6)

7-7: Spot-on new import, boss!
The addition of OnboardingStatus fits neatly, just like a bold punch in the right spot. Everything looks perfect at first glance.


12-12: Renaming the service – a heroic move!
The rename to SpenderOnboardingService is grand, da! Makes the intention crisp and clear.


15-17: Time for a quick check!
The new getOnboardingStatus returning OnboardingStatus is correct as per the updated endpoint. Ensure all calling modules handle the new response type, my friend.


23-23: Endpoint updates are shining bright like a Superstar!
/onboarding/process_step_connect_cards aligns with the new route. Code is strong, boss.


31-31: SMS Opt-In is looking solid!
/onboarding/process_step_sms_opt_in is correct. Make sure you have enough testing coverage.


37-37: Welcome modal route – a clean finish!
/onboarding/process_step_show_welcome_modal is consistent with the rest. Properly orchestrated, my friend.

src/app/fyle/spender-onboarding/spender-onboarding.page.ts (2)

35-72: Smooth concurrency approach, da!
Using forkJoin to load user data, org settings, onboarding status, and corporate card info in parallel is brilliance. Just be cautious about error handling if one of them fails.


74-81: Skipping steps, Rajini style!
The logic is straightforward. Confirm that skipping triggers the correct transitions in the UI, macha.

src/app/fyle/fyle-routing.module.ts (1)

48-52: New route in town, boss!
Aiyo! 'spender_onboarding' path is well placed for lazy loading. Test the navigation thoroughly to ensure unstoppable user flow.

src/app/fyle/spender-onboarding/spender-onboarding.page.spec.ts (5)

1-14: Superb Import Statements, Thalaiva!

All necessary dependencies are well organized and clearly declared. This is good usage of Angular testing modules, da.


15-24: Aiyoo, Setup Looking Solid!

The spy objects and service dependencies are well structured in these lines. Excellent approach for isolating and mocking the required services, da.


25-64: TestBed Configuration Flows Smoothly, Boss!

Wonderful usage of TestBed.configureTestingModule to provide the correct spies. This ensures each test runs in a clean environment.


87-95: Skipping the Steps Like a Superstar!

Implementation for skipping steps is perfect:

  1. Correct calls to the service methods based on the current step.
  2. This test ensures each skip function is tested for different steps.

No changes required.


97-105: Marking Steps as Complete Like Lightning!

Super job verifying the correct service methods are called for each step. The test coverage is thorough, da.

src/app/core/services/spender-onboarding.service.spec.ts (11)

1-8: Top-Tier Imports for Testing, Boss!

Ensures you have everything needed: from test modules to final boarding step models. Perfect.


9-30: Setting Up the Spies with Style!

The beforeEach method properly injects the dependency, which is absolutely superb for repeatable tests. Perfect clarity on what is being mocked.


32-41: Aiyoo, Onboarding Status is in Good Hands!

This test verifies we get the right data from '/onboarding'. Crisp and clean.


43-57: Process Connect Cards Step: Mass Hero Entry!

  1. Perfect usage of a mocked post call.
  2. Good job verifying that you pass the correct endpoint and data.

All is well, Superstar.


59-73: Process SMS Opt-In Step: Mass Scenes Continue!

Checks everything from the returned result to the endpoint. Great alignment with the rest of the code.


75-88: Welcome Modal Step: Applause!

Neatly tests the flow and correctness of the posted data. Solid approach, boss.


90-99: Marking Welcome Modal Step As Complete: Smooth as Silk!

The spy’s usage is correct, validating the data flow thoroughly.


101-110: Connect Cards Step is a Full-On Success!

Neatly verifies the next step call. Good job ensuring the result is the same as the mock.


112-121: Skipping Cards, Mass Style!

  1. The skip function calls the process method with is_skipped: true.
  2. Return value is verified thoroughly.

Tip-top, Machi!


123-132: Marking SMS Opt-In Step As Complete: Bliss!

Aye, usage is consistent, verifying the correct method call.


134-144: Skipping SMS Opt-In: Perfect Execution!

The final test ensures coverage for all skip scenarios. You’ve covered everything, boss.

src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts (11)

23-34: Component Decorator: Yes, Mokka!

The selector, template, and style are well-defined. Perfect approach for a dedicated step component.


35-45: Fabulous Card Data Structures, Boss!

Variables are properly typed: outputs, inputs, form controls. Great job.


46-52: Popover Cards List: Clear as Crystal!

The structure is well-defined for success/failure tracking.


54-61: Lovely Setup in Constructor, Superstar!

Injecting the necessary services is done well, so no extra overhead is present.


63-86: enrollCards(): Superstar Implementation!

  1. Using concatMap to sequentially enroll each card is wise.
  2. The cardsList approach to track success/failure is easy to read.
  3. Emitting isStepCompleted after success is perfect.

All top standard, my friend.


104-134: showErrorPopover(): Cinematic Flair!

  1. The popover usage is neat: gives user chance to proceed or cancel.
  2. Good DRY approach of using generateMessage() for the text.

Keep rocking, Superstar.


136-141: OnChanges Hook: Great Sensible Use!

You properly enable/disable flags based on orgSettings. Crisp logic, no wasted checks.


143-172: ngOnInit: Marvelous Work with Dynamic Form Controls!

  1. Filtering out the enrollable cards is a neat approach.
  2. Adding form controls for each card is well-coded.

No major issues, Thalaiva.


174-190: Card Number Formatting: Style is Slick!

  1. Removing non-numeric characters.
  2. Grouping digits in sets of 4.

Super user-friendly approach.


192-205: Card Number Validator: Very Good!

It checks valid numbers and enumerates the card type. Thorough job.


207-219: Card Network Validator: Another Round of Applause!

  1. Checks if Visa/Mastercard RTF is enabled.
  2. Returns a consistent error object.

Well implemented, Superstar.

src/app/fyle/spender-onboarding/spender-onboarding.page.html (3)

1-10: HTML Structure is Entertaining and Efficient!

  1. Using *ngIf="!isLoading" for conditional rendering is genius.
  2. The user greeting with wave is endearing.

All good, my friend.


12-26: Stepper Container is Superb, da!

A dual progress bar approach is visually appealing. The Skip CTA is placed well, connecting to a click method that is easily discovered.


28-33: Connect Card Step Component Integration: Classy Move!

Props to you for binding [orgSettings] and hooking into (isStepCompleted) to skip the next step. This ensures modular flow.

@@ -1,5 +1,7 @@
import { Component, Input } from '@angular/core';
import { PopoverController } from '@ionic/angular';
import { CardNetworkType } from 'src/app/core/enums/card-network-type';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

"Unused import, simplify life da!"
You’re importing CardNetworkType but not actually using it. Time to remove that if not needed.

- import { CardNetworkType } from 'src/app/core/enums/card-network-type';
🧰 Tools
🪛 eslint

[error] 3-3: 'CardNetworkType' is defined but never used.

(@typescript-eslint/no-unused-vars)

import { SharedModule } from 'src/app/shared/shared.module';
import { SpenderOnboardingPage } from './spender-onboarding.page';
import { IonicModule } from '@ionic/angular';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Duplicate FormsModule imports, boss!
Aiyo! FormsModule is imported twice, superstar! Removing the extra import can keep things clean like a swift knockout punch.

  imports: [
    SharedModule,
    CommonModule,
    FormsModule,
    IonicModule,
    MatButtonModule,
    SpenderOnboardingRoutingModule,
-   FormsModule,
    ReactiveFormsModule,
  ],

Also applies to: 19-19

Comment on lines 83 to 90
markStepAsComplete(): void {
if (this.currentStep === OnboardingStep.CONNECT_CARD) {
this.spenderOnboardingService.markConnectCardsStepAsComplete();
}
if (this.currentStep === OnboardingStep.OPT_IN) {
this.spenderOnboardingService.markSmsOptInStepAsComplete();
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Marking steps to success!
All good, boss—but ensure the backend handles parallel calls if the user taps quickly to skip or complete. Watch for concurrency issues, my friend.

Comment on lines 1 to 3
import { Component } from '@angular/core';
import { forkJoin, from, map, switchMap } from 'rxjs';
import { ExtendedOrgUser } from 'src/app/core/models/extended-org-user.model';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Unused import, Thalaivar!
ExtendedOrgUser is declared but not used. Let’s remove it to keep everything neat—like a polished blockbuster.

- import { ExtendedOrgUser } from 'src/app/core/models/extended-org-user.model';
+ // Removed since not referenced
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Component } from '@angular/core';
import { forkJoin, from, map, switchMap } from 'rxjs';
import { ExtendedOrgUser } from 'src/app/core/models/extended-org-user.model';
import { Component } from '@angular/core';
import { forkJoin, from, map, switchMap } from 'rxjs';
// Removed since not referenced
🧰 Tools
🪛 eslint

[error] 3-3: 'ExtendedOrgUser' is defined but never used.

(@typescript-eslint/no-unused-vars)

Comment on lines 66 to 85
it('ionViewWillEnter(): should show loader and fetch onboarding data on ionViewWillEnter', (done) => {
loaderService.showLoader.and.resolveTo();
orgUserService.getCurrent.and.returnValue(of(extendedOrgUserResponse));
orgSettingsService.get.and.returnValue(of(orgSettingsData));
spenderOnboardingService.getOnboardingStatus.and.returnValue(of(onboardingStatusData));
corporateCreditCardExpenseService.getCorporateCards.and.returnValue(of([]));

component.ionViewWillEnter();

fixture.whenStable().then(() => {
fixture.detectChanges();

expect(loaderService.showLoader).toHaveBeenCalledTimes(1);
expect(component.userFullName).toBe('Aiyush');
expect(component.currentStep).toBe(OnboardingStep.CONNECT_CARD);
expect(component.isLoading).toBeFalse();
expect(loaderService.hideLoader).toHaveBeenCalled();
done();
});
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Aha! Checking the Loader and Onboarding Data is Marvelous!

  1. The test flow is crisp: show loader, fetch user data, set component fields, hide loader.
  2. Ensuring the user name and step are set is top-class.

Everything looks top-notch, but do be sure there are fallback checks in case data is not returned.

Comment on lines 131 to 140
&__input-errors {
color: $red;
font-size: 12px;
line-height: 1.3;

& > :not(:first-child) {
// Only show one error message at a time
display: none;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Mind it! Let's make our styles accessible to all!

The error messages need some style power-up for better accessibility, thalaiva!

Here's how we roll with accessibility:

  &__input-errors {
    color: $red;
    font-size: 12px;
    line-height: 1.3;
+   // Add for screen readers
+   &::before {
+     content: "Error: ";
+     clip: rect(1px, 1px, 1px, 1px);
+     position: absolute;
+   }

    & > :not(:first-child) {
      display: none;
    }
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
&__input-errors {
color: $red;
font-size: 12px;
line-height: 1.3;
& > :not(:first-child) {
// Only show one error message at a time
display: none;
}
}
&__input-errors {
color: $red;
font-size: 12px;
line-height: 1.3;
// Add for screen readers
&::before {
content: "Error: ";
clip: rect(1px, 1px, 1px, 1px);
position: absolute;
}
& > :not(:first-child) {
// Only show one error message at a time
display: none;
}
}

Comment on lines 75 to 105
&__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;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Style like a boss! Let's make these styles more powerful!

The input container styles are looking sharp, but let's make them even more maintainable with CSS custom properties, macha!

Here's a style transformation that would make even superstar proud:

+ :root {
+   --input-border-color: #{$grey};
+   --input-border-color-focus: #{$black-light};
+   --input-border-color-error: #{$red};
+ }

  &__input-container {
    padding: 16px 16px 8px 16px;
    border-radius: 8px;
-   border: 1px solid $grey;
+   border: 1px solid var(--input-border-color);

    &:focus-within:not(&--error) {
-     border: 1px solid $black-light;
+     border: 1px solid var(--input-border-color-focus);
    }
  }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 64 to 90
<span *ngIf="fg.controls['card_number_' + i]?.errors.invalidCardNumber"
>Please enter a valid card number.</span
>

<ng-container *ngIf="fg.controls['card_number_' + i]?.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
>

<ng-template #visaOnlyOrg>
<!-- Check if only visa is enabled -->
<span
*ngIf="
fg.controls['card_number_' + i]?.errors.invalidCardNetwork && isVisaRTFEnabled;
else mastercardOnlyOrg
"
>Enter a valid Visa number. If you have other cards, please contact your admin.</span
>
</ng-template>

<ng-template #mastercardOnlyOrg>
<!-- Check if only mastercard is enabled -->
<span *ngIf="fg.controls['card_number_' + i]?.errors.invalidCardNetwork && isMastercardRTFEnabled"
>Enter a valid Mastercard number. If you have other cards, please contact your admin.</span
>
</ng-template>
</ng-container>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Time to simplify these error messages like a boss!

These nested error messages are more complex than my punch dialogues! Let's make them simple and powerful!

Move the error message logic to the component:

// In component
getErrorMessage(index: number): string {
  const control = this.fg.controls[`card_number_${index}`];
  if (control?.errors?.invalidCardNumber) {
    return 'Please enter a valid card number.';
  }
  if (control?.errors?.invalidCardNetwork) {
    if (this.isVisaRTFEnabled && this.isMastercardRTFEnabled) {
      return 'Enter a valid Visa or Mastercard number. If you have other cards, please contact your admin.';
    }
    if (this.isVisaRTFEnabled) {
      return 'Enter a valid Visa number. If you have other cards, please contact your admin.';
    }
    if (this.isMastercardRTFEnabled) {
      return 'Enter a valid Mastercard number. If you have other cards, please contact your admin.';
    }
  }
  return '';
}

Then simplify the template:

- <span *ngIf="fg.controls['card_number_' + i]?.errors.invalidCardNumber">Please enter a valid card number.</span>
- <ng-container *ngIf="fg.controls['card_number_' + i]?.errors.invalidCardNetwork">
-   <!-- Complex nested conditions -->
- </ng-container>
+ <span>{{ getErrorMessage(i) }}</span>

Comment on lines 98 to 106
<ion-button
class="btn-primary connect-card__primary-cta"
fill="clear"
aria-label="Navigate back to sign in page"
role="button"
(click)="enrollCards()"
>
Continue
</ion-button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Mind it! This button needs some style!

The continue button's aria-label is talking about signing in when it should be talking about enrollment! Also, where's the loading state, thalaiva?

Let's fix this like a boss:

  <ion-button
    class="btn-primary connect-card__primary-cta"
    fill="clear"
-   aria-label="Navigate back to sign in page"
+   aria-label="Continue with card enrollment"
    role="button"
+   [disabled]="fg.invalid || isLoading"
    (click)="enrollCards()"
  >
-   Continue
+   <span>{{ isLoading ? 'Enrolling...' : 'Continue' }}</span>
  </ion-button>

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 8 to 29
<form formGroup="fg" *ngIf="fg">
<div *ngIf="enrollableCards?.length > 0">
<div *ngFor="let card of enrollableCards; let i = index">
<div class="connect-card__input-label">
<span>Corporate card</span>
</div>

<div
class="connect-card__input-inner-container"
[ngClass]="{ 'connect-card__input-inner-container--error': false }"
>
<input
class="smartlook-show connect-card__card-number-input pl-0"
inputmode="numeric"
placeholder="xxxx xxxx xxxx"
data-testid="card-number-input"
appAutofocus
[timeout]="500"
[formControlName]="'card_number_' + i"
required
(input)="onCardNumberUpdate(card, 'card_number_' + i)"
/>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Listen up! The form needs some extra punch!

Your form validation is strong, but let's make it stronger than vibhuti!

Add these power-packed attributes to level up your form accessibility:

- <form formGroup="fg" *ngIf="fg">
+ <form formGroup="fg" *ngIf="fg" (ngSubmit)="enrollCards()" novalidate>
  <div *ngIf="enrollableCards?.length > 0">
    <div *ngFor="let card of enrollableCards; let i = index">
      <div class="connect-card__input-label">
-       <span>Corporate card</span>
+       <span [attr.id]="'card-label-' + i">Corporate card {{i + 1}}</span>
      </div>
      <input
        class="smartlook-show connect-card__card-number-input pl-0"
        inputmode="numeric"
        placeholder="xxxx xxxx xxxx"
        data-testid="card-number-input"
+       [attr.aria-labelledby]="'card-label-' + i"
+       [attr.aria-invalid]="fg.controls['card_number_' + i]?.invalid && fg.controls['card_number_' + i]?.touched"
        appAutofocus
        [timeout]="500"
        [formControlName]="'card_number_' + i"
        required
        (input)="onCardNumberUpdate(card, 'card_number_' + i)"
      />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<form formGroup="fg" *ngIf="fg">
<div *ngIf="enrollableCards?.length > 0">
<div *ngFor="let card of enrollableCards; let i = index">
<div class="connect-card__input-label">
<span>Corporate card</span>
</div>
<div
class="connect-card__input-inner-container"
[ngClass]="{ 'connect-card__input-inner-container--error': false }"
>
<input
class="smartlook-show connect-card__card-number-input pl-0"
inputmode="numeric"
placeholder="xxxx xxxx xxxx"
data-testid="card-number-input"
appAutofocus
[timeout]="500"
[formControlName]="'card_number_' + i"
required
(input)="onCardNumberUpdate(card, 'card_number_' + i)"
/>
<form formGroup="fg" *ngIf="fg" (ngSubmit)="enrollCards()" novalidate>
<div *ngIf="enrollableCards?.length > 0">
<div *ngFor="let card of enrollableCards; let i = index">
<div class="connect-card__input-label">
<span [attr.id]="'card-label-' + i">Corporate card {{i + 1}}</span>
</div>
<div
class="connect-card__input-inner-container"
[ngClass]="{ 'connect-card__input-inner-container--error': false }"
>
<input
class="smartlook-show connect-card__card-number-input pl-0"
inputmode="numeric"
placeholder="xxxx xxxx xxxx"
data-testid="card-number-input"
[attr.aria-labelledby]="'card-label-' + i"
[attr.aria-invalid]="fg.controls['card_number_' + i]?.invalid && fg.controls['card_number_' + i]?.touched"
appAutofocus
[timeout]="500"
[formControlName]="'card_number_' + i"
required
(input)="onCardNumberUpdate(card, 'card_number_' + i)"
/>

@github-actions github-actions bot added the size/L Large PR label Dec 26, 2024
Copy link

PR description must contain a link to a ClickUp (case-insensitive)

1 similar comment
Copy link

PR description must contain a link to a ClickUp (case-insensitive)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 648f6c0 and cb873fd.

📒 Files selected for processing (2)
  • src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts (2 hunks)
  • src/app/fyle/spender-onboarding/spender-onboarding.page.html (1 hunks)
🔇 Additional comments (2)
src/app/fyle/spender-onboarding/spender-onboarding.page.html (1)

29-32: Mind it! The component integration looks super stylish! 🌟

The way you've connected the component with proper input and output bindings is as smooth as my signature moves! The orgSettings input and isStepCompleted event handler are perfectly choreographed.

src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts (1)

1-21: Mind it! Clean up those unused imports, macha!

The following imports are not being used in the code:

  • FormArray
  • finalize
  • statementUploadedCard
  • visaRTFCard

Let's keep our code as clean as my style! 😎

Comment on lines +28 to +33
<div class="spender-onboarding__component-container">
<app-spender-onboarding-connect-card-step
[orgSettings]="orgSettings"
(isStepCompleted)="skipOnboardingStep()"
></app-spender-onboarding-connect-card-step>
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Hey boss! Let's add some loading state magic! ✨

The component container could use a loading state to handle those micro-moments when the card data is being fetched. When you're as fast as lightning like me, every millisecond counts!

Here's a style-packed solution:

 <div class="spender-onboarding__component-container">
+  <div *ngIf="isCardDataLoading" class="spender-onboarding__loading">
+    <ion-spinner name="crescent"></ion-spinner>
+  </div>
   <app-spender-onboarding-connect-card-step
     [orgSettings]="orgSettings"
     (isStepCompleted)="skipOnboardingStep()"
+    *ngIf="!isCardDataLoading"
   ></app-spender-onboarding-connect-card-step>
 </div>

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 89 to 103
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.`;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Aiyyo! We've got a duplicate condition here, machan!

The first two conditions are exactly the same:

if (this.cardsList.successfulCards.length > 0) {
  // First block
} else if (this.cardsList.successfulCards.length > 0) {
  // Second block
}

This means the second block will never execute! 🎭

Here's how to fix it with style:

- if (this.cardsList.successfulCards.length > 0) {
+ if (this.cardsList.successfulCards.length > 0 && this.cardsList.failedCards.length > 1) {
  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.successfulCards.length > 0 && this.cardsList.failedCards.length === 1) {

Comment on lines +144 to +173
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();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Looking sharp! But let's make it TypeScript-style strong, partner! 💪

The form initialization is solid, but we can make it even better with proper TypeScript types for the form controls.

+ interface CardFormControls {
+   [key: `card_number_${number}`]: FormControl<string>;
+ }

- fg: FormGroup;
+ fg: FormGroup<CardFormControls>;

Also, consider using the new typed forms feature when upgrading to Angular 14+:

this.fg = this.fb.group<CardFormControls>({});

Comment on lines +105 to +134
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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Mind it! Another subscription needs cleanup, machan!

The subscription in showErrorPopover() also needs proper cleanup. Style is in the details! 😎

Modify your subscription:

- .subscribe(noop);
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(noop);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +64 to +87
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);
}
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ey! Don't forget to clean up those subscriptions, partner!

The subscription in enrollCards() needs proper cleanup to prevent memory leaks. Let's handle it with style!

Add this to your class:

+ private destroy$ = new Subject<void>();

  ngOnDestroy(): void {
+   this.destroy$.next();
+   this.destroy$.complete();
  }

Then modify your subscription:

- .subscribe(() => {
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(() => {

Committable suggestion skipped: line range outside the PR's diff.

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the same condition on both the if and else if? Even code rabbit did not notice this :D @bistaastha

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be for the card or for the cards no? better be for the card(s)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixing this in follow up

Copy link
Contributor

@arjunaj5 arjunaj5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one comment

Copy link

PR description must contain a link to a ClickUp (case-insensitive)

Copy link

PR description must contain a link to a ClickUp (case-insensitive)

Copy link

PR description must contain a link to a ClickUp (case-insensitive)

Copy link

PR description must contain a link to a ClickUp (case-insensitive)

Copy link

PR description must contain a link to a ClickUp (case-insensitive)

Copy link

Unit Test Coverage % values
Statements 95.98% ( 19362 / 20171 )
Branches 91.1% ( 10682 / 11725 )
Functions 94.25% ( 5762 / 6113 )
Lines 96.03% ( 18490 / 19254 )

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb873fd and b8cf711.

📒 Files selected for processing (4)
  • src/app/core/models/popover-cards-list.model.ts (1 hunks)
  • src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts (2 hunks)
  • src/app/shared/components/popup-alert/popup-alert.component.scss (1 hunks)
  • src/app/shared/components/popup-alert/popup-alert.component.ts (2 hunks)
🧰 Additional context used
🪛 GitHub Check: Run linters
src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts

[failure] 4-4:
'FormArray' is defined but never used


[failure] 12-12:
'finalize' is defined but never used


[failure] 14-14:
'statementUploadedCard' is defined but never used


[failure] 14-14:
'visaRTFCard' is defined but never used

🪛 eslint
src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts

[error] 4-4: 'FormArray' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 12-12: 'finalize' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 14-14: 'statementUploadedCard' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 14-14: 'visaRTFCard' is defined but never used.

(@typescript-eslint/no-unused-vars)

🔇 Additional comments (9)
src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts (4)

64-87: Oi, Remember to Clean Up That Subscription, Machan!

This .subscribe in enrollCards() can cause memory leaks if the component sticks around. Pipe it with takeUntil(destroy$) and destroy it in ngOnDestroy(). This was pointed out previously too, so let's keep it short.


105-135: Subscription Cleanup Again, Superstar!

Same show in showErrorPopover()—it’s best to pipe our subscription with takeUntil(destroy$). Doing so ensures no leftover subscriptions that could hamper performance.


89-103: Check Your Message Logic, Da!

When this.cardsList.successfulCards.length > 0, you show a general error. Else if this.cardsList.failedCards.length > 0, you show a different message. The final else block references this.cardsList.failedCards again. Verify whether any scenario ends up in that final else accidentally. Might want to unify or clarify the conditions, machan.


144-173: Mightier with Typed Forms, Thalaiva!

You’re programmatically creating form controls. If you’re on Angular 14 or higher, typed reactive forms can add more power and clarity. But you’ve heard this tune before, yeah?

src/app/shared/components/popup-alert/popup-alert.component.ts (2)

3-3: Import Looks Good, But Verify Necessity, Da!

PopoverCardsList is used for the new input property below. If there's no usage beyond that, it's fine. If you plan to manipulate card statuses within this component, proceed, else reconsider. Keep code crisp, machan.


20-20: Well-Structured Inputs and Method, Superstar!

Adding @Input() cardsList: PopoverCardsList and the typed ctaClickedEvent(action: string): void is neat. This improves clarity for your popover interactions. Simply glorious, da!

Also applies to: 24-24

src/app/core/models/popover-cards-list.model.ts (1)

1-4: Sparkling Clean Interface, Machan!

This PopoverCardsList structure is tight and crisp. Perfect for consolidating success and failure states. Carry on, boss!

src/app/shared/components/popup-alert/popup-alert.component.scss (2)

57-109: Impressive Visuals, Superstar!

These success/error tick containers stand out well. Great job giving users strong visual feedback!


90-100: 🧹 Nitpick (assertive)

Duplicate Padding, Thalaiva!

You're setting padding: 12px; twice in .popup-alert--content-container. Remove one to keep a single punch, da!

  &--content-container {
    ...
    padding: 12px;
    ...
-   padding: 12px;
    ...
  }

Likely invalid or redundant comment.

import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
import {
AbstractControl,
FormArray,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Aiyyo, Unused Imports Detected, Da!

FormArray, finalize, statementUploadedCard, and visaRTFCard are not used anywhere. Time to remove them and keep your code dancing light, machan!

- import {
-   FormArray,
-   ...
- } from '@angular/forms';

- import { catchError, concatMap, finalize, from, ... } from 'rxjs';

- import { statementUploadedCard, visaRTFCard } from 'src/app/core/mock-data/platform-corporate-card.data';

Also applies to: 12-12, 14-14

🧰 Tools
🪛 GitHub Check: Run linters

[failure] 4-4:
'FormArray' is defined but never used

🪛 eslint

[error] 4-4: 'FormArray' is defined but never used.

(@typescript-eslint/no-unused-vars)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size/L Large PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants