-
Notifications
You must be signed in to change notification settings - Fork 0
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: UI for the new "Conencted" badge #1144
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { TestBed } from '@angular/core/testing'; | ||
|
||
import { IntegrationsService } from './integrations.service'; | ||
|
||
xdescribe('IntegrationsService', () => { | ||
let service: IntegrationsService; | ||
|
||
beforeEach(() => { | ||
TestBed.configureTestingModule({}); | ||
service = TestBed.inject(IntegrationsService); | ||
}); | ||
|
||
it('should be created', () => { | ||
expect(service).toBeTruthy(); | ||
}); | ||
}); | ||
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,22 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
import { Injectable } from '@angular/core'; | ||||||||||||||||||||||||||||||||||||||||||||||
import { AppUrl } from '../../models/enum/enum.model'; | ||||||||||||||||||||||||||||||||||||||||||||||
import { ApiService } from './api.service'; | ||||||||||||||||||||||||||||||||||||||||||||||
import { HelperService } from './helper.service'; | ||||||||||||||||||||||||||||||||||||||||||||||
import { Observable } from 'rxjs'; | ||||||||||||||||||||||||||||||||||||||||||||||
import { Integration } from '../../models/integrations/integrations.model'; | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
@Injectable({ | ||||||||||||||||||||||||||||||||||||||||||||||
providedIn: 'root' | ||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||
export class IntegrationsService { | ||||||||||||||||||||||||||||||||||||||||||||||
constructor( | ||||||||||||||||||||||||||||||||||||||||||||||
private apiService: ApiService, | ||||||||||||||||||||||||||||||||||||||||||||||
private helper: HelperService | ||||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
getIntegrations(): Observable<Integration[]> { | ||||||||||||||||||||||||||||||||||||||||||||||
this.helper.setBaseApiURL(AppUrl.INTEGRATION); | ||||||||||||||||||||||||||||||||||||||||||||||
return this.apiService.get(`/integrations/`, {}); | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+18
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add error handling and retry logic. The
getIntegrations(): Observable<Integration[]> {
this.helper.setBaseApiURL(AppUrl.INTEGRATION);
- return this.apiService.get(`/integrations/`, {});
+ return this.apiService.get<Integration[]>('/integrations/', {}).pipe(
+ retry({
+ count: 3,
+ delay: (error, retryCount) => {
+ if (error.status === 404) {
+ return EMPTY;
+ }
+ return timer(1000 * retryCount);
+ }
+ }),
+ catchError((error: HttpErrorResponse) => {
+ console.error('Error fetching integrations:', error);
+ return throwError(() => error);
+ })
+ );
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ <h3 class="landing-v2--section-heading"> | |
href="mailto:[email protected]">support{{'@'}}fylehq.com</a> | ||
</p> | ||
</header> | ||
<div class="tw-flex tw-text-14-px tw-border-y tw-border-separator"> | ||
<div *ngIf="!exposeC1Apps" class="tw-flex tw-text-14-px tw-border-y tw-border-separator"> | ||
<div class="landing-v2--tab" [ngClass]="{'tw-text-menu-inactive-text-color': !integrationTabs.ALL}" | ||
(click)="switchView(IntegrationsView.ALL)"> | ||
All | ||
|
@@ -41,9 +41,13 @@ <h3 class="landing-v2--section-heading"> | |
<div *ngIf="isAppShown('NETSUITE')"> | ||
<div class="landing-v2--accounting-app" | ||
(click)="openAccountingIntegrationApp(AccountingIntegrationApp.NETSUITE)"> | ||
<div class="tw-flex tw-justify-between"> | ||
<img src="assets/logos/netsuite-logo.png" /> | ||
<button class="btn-connect">Connect</button> | ||
<div class="tw-flex tw-justify-between tw-items-center"> | ||
<img src="assets/logos/netsuite-logo-new.png" /> | ||
@if (isAppConnected('NETSUITE')) { | ||
<app-badge text="Connected" [theme]="ThemeOption.SUCCESS"></app-badge> | ||
} @else { | ||
<button class="btn-connect">Connect</button> | ||
} | ||
</div> | ||
<div> | ||
<span class="landing-v2--accounting-app-name"> | ||
|
@@ -57,9 +61,13 @@ <h3 class="landing-v2--section-heading"> | |
</div> | ||
<div *ngIf="isAppShown('INTACCT')" class="landing-v2--accounting-app" | ||
(click)="openAccountingIntegrationApp(AccountingIntegrationApp.SAGE_INTACCT)"> | ||
<div class="tw-flex tw-justify-between"> | ||
<img src="assets/logos/intacct-logo.png" /> | ||
<button class="btn-connect">Connect</button> | ||
<div class="tw-flex tw-justify-between tw-items-center"> | ||
<img src="assets/logos/intacct-logo-new.png" /> | ||
@if (isAppConnected('INTACCT')) { | ||
<app-badge text="Connected" [theme]="ThemeOption.SUCCESS"></app-badge> | ||
} @else { | ||
<button class="btn-connect">Connect</button> | ||
} | ||
</div> | ||
<div> | ||
<span class="landing-v2--accounting-app-name"> | ||
|
@@ -72,9 +80,13 @@ <h3 class="landing-v2--section-heading"> | |
</div> | ||
<div *ngIf="isAppShown('QBO')" class="landing-v2--accounting-app" | ||
(click)="openAccountingIntegrationApp(AccountingIntegrationApp.QBO)"> | ||
<div class="tw-flex tw-justify-between"> | ||
<div class="tw-flex tw-justify-between tw-items-center"> | ||
<img src="assets/logos/quickbooks-logo.png" class="!tw-h-[30.7px]" /> | ||
<button class="btn-connect">Connect</button> | ||
@if (isAppConnected('QBO')) { | ||
<app-badge text="Connected" [theme]="ThemeOption.SUCCESS"></app-badge> | ||
} @else { | ||
<button class="btn-connect">Connect</button> | ||
} | ||
</div> | ||
<div> | ||
<span class="landing-v2--accounting-app-name"> | ||
|
@@ -87,9 +99,13 @@ <h3 class="landing-v2--section-heading"> | |
</div> | ||
<div *ngIf="isAppShown('XERO')" class="landing-v2--accounting-app" | ||
(click)="openAccountingIntegrationApp(AccountingIntegrationApp.XERO)"> | ||
<div class="tw-flex tw-justify-between"> | ||
<img src="assets/logos/xero-logo.png" /> | ||
<button class="btn-connect">Connect</button> | ||
<div class="tw-flex tw-justify-between tw-items-center"> | ||
<img src="assets/logos/xero-logo-new.png" /> | ||
@if (isAppConnected('XERO')) { | ||
<app-badge text="Connected" [theme]="ThemeOption.SUCCESS"></app-badge> | ||
} @else { | ||
<button class="btn-connect">Connect</button> | ||
} | ||
</div> | ||
<div> | ||
<span class="landing-v2--accounting-app-name"> | ||
|
@@ -102,9 +118,13 @@ <h3 class="landing-v2--section-heading"> | |
</div> | ||
<div *ngIf="isAppShown('QBD')" class="landing-v2--accounting-app" | ||
(click)="openInAppIntegration(InAppIntegration.QBD)"> | ||
<div class="tw-flex tw-justify-between"> | ||
<div class="tw-flex tw-justify-between tw-items-center"> | ||
<img src="assets/logos/quickbooks-logo.png" class="!tw-h-[30.7px]" /> | ||
<button class="btn-connect">Connect</button> | ||
@if (isAppConnected('QBD')) { | ||
<app-badge text="Connected" [theme]="ThemeOption.SUCCESS"></app-badge> | ||
} @else { | ||
<button class="btn-connect">Connect</button> | ||
} | ||
</div> | ||
<div> | ||
<span class="landing-v2--accounting-app-name"> | ||
|
@@ -118,9 +138,13 @@ <h3 class="landing-v2--section-heading"> | |
<!-- Direct --> | ||
<div *ngIf="isAppShown('QBD_DIRECT')" class="landing-v2--accounting-app" | ||
(click)="openInAppIntegration(InAppIntegration.QBD_DIRECT)"> | ||
<div class="tw-flex tw-justify-between"> | ||
<div class="tw-flex tw-justify-between tw-items-center"> | ||
<img src="assets/logos/quickbooks-logo.png" class="!tw-h-[30.7px]" /> | ||
<button class="btn-connect">Connect</button> | ||
@if (isAppConnected('QBD_DIRECT')) { | ||
<app-badge text="Connected" [theme]="ThemeOption.SUCCESS"></app-badge> | ||
} @else { | ||
<button class="btn-connect">Connect</button> | ||
} | ||
</div> | ||
<span class="landing-v2--accounting-app-name tw-items-center tw-gap-4"> | ||
<div> | ||
|
@@ -132,9 +156,13 @@ <h3 class="landing-v2--section-heading"> | |
</div> | ||
<div *ngIf="isAppShown('SAGE300')" class="landing-v2--accounting-app" | ||
(click)="openInAppIntegration(InAppIntegration.SAGE300)"> | ||
<div class="tw-flex tw-justify-between"> | ||
<div class="tw-flex tw-justify-between tw-items-center"> | ||
<img src="assets/logos/sage300-logo.png" class="tw-py-[4px]" /> | ||
<button class="btn-connect">Connect</button> | ||
@if (isAppConnected('SAGE300')) { | ||
<app-badge text="Connected" [theme]="ThemeOption.SUCCESS"></app-badge> | ||
} @else { | ||
<button class="btn-connect">Connect</button> | ||
} | ||
</div> | ||
<span class="landing-v2--accounting-app-name tw-items-center tw-gap-4"> | ||
<div> | ||
|
@@ -147,9 +175,13 @@ <h3 class="landing-v2--section-heading"> | |
</div> | ||
<div *ngIf="isAppShown('BUSINESS_CENTRAL')" class="landing-v2--accounting-app" | ||
(click)="openInAppIntegration(InAppIntegration.BUSINESS_CENTRAL)"> | ||
<div class="tw-flex tw-justify-between"> | ||
<div class="tw-flex tw-justify-between tw-items-center"> | ||
<img src="assets/logos/BusinessCentral-logo.svg" /> | ||
<button class="btn-connect">Connect</button> | ||
@if (isAppConnected('BUSINESS_CENTRAL')) { | ||
<app-badge text="Connected" [theme]="ThemeOption.SUCCESS"></app-badge> | ||
} @else { | ||
<button class="btn-connect">Connect</button> | ||
} | ||
</div> | ||
<span class="landing-v2--accounting-app-name tw-items-center tw-gap-4"> | ||
<div> | ||
|
@@ -163,9 +195,13 @@ <h3 class="landing-v2--section-heading"> | |
|
||
<div *ngIf="isAppShown('BAMBOO_HR')"> | ||
<div class="landing-v2--accounting-app" (click)="openInAppIntegration(InAppIntegration.BAMBOO_HR)"> | ||
<div class="tw-flex tw-justify-between"> | ||
<div class="tw-flex tw-justify-between tw-items-center"> | ||
<img src="assets/logos/bamboo-hr-logo.png" /> | ||
<button class="btn-connect">Connect</button> | ||
@if (isAppConnected('BAMBOO_HR')) { | ||
<app-badge text="Connected" [theme]="ThemeOption.SUCCESS"></app-badge> | ||
} @else { | ||
<button class="btn-connect">Connect</button> | ||
} | ||
</div> | ||
<div> | ||
<span class="landing-v2--accounting-app-name"> | ||
|
@@ -180,9 +216,13 @@ <h3 class="landing-v2--section-heading"> | |
|
||
<div *ngIf="isAppShown('TRAVELPERK')"> | ||
<div class="landing-v2--accounting-app" (click)="openInAppIntegration(InAppIntegration.TRAVELPERK)"> | ||
<div class="tw-flex tw-justify-between"> | ||
<div class="tw-flex tw-justify-between tw-items-center"> | ||
<img src="assets/logos/travelperk-logo.png" class="tw-py-[5px]" /> | ||
<button class="btn-connect">Connect</button> | ||
@if (isAppConnected('TRAVELPERK')) { | ||
<app-badge text="Connected" [theme]="ThemeOption.SUCCESS"></app-badge> | ||
} @else { | ||
<button class="btn-connect">Connect</button> | ||
} | ||
</div> | ||
<div> | ||
<span class="landing-v2--accounting-app-name"> | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -15,6 +15,7 @@ import { QboAuthService } from 'src/app/core/services/qbo/qbo-core/qbo-auth.serv | |||||
import { XeroAuthService } from 'src/app/core/services/xero/xero-core/xero-auth.service'; | ||||||
import { exposeAppConfig } from 'src/app/branding/expose-app-config'; | ||||||
import { NetsuiteAuthService } from 'src/app/core/services/netsuite/netsuite-core/netsuite-auth.service'; | ||||||
import { IntegrationsService } from 'src/app/core/services/common/integrations.service'; | ||||||
|
||||||
@Component({ | ||||||
selector: 'app-landing-v2', | ||||||
|
@@ -31,6 +32,10 @@ export class LandingV2Component implements OnInit { | |||||
|
||||||
org: Org = this.orgService.getCachedOrg(); | ||||||
|
||||||
private connectedApps: IntegrationAppKey[]; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Initialize the connectedApps array to prevent undefined access. The - private connectedApps: IntegrationAppKey[];
+ private connectedApps: IntegrationAppKey[] = []; 📝 Committable suggestion
Suggested change
|
||||||
|
||||||
readonly exposeC1Apps = brandingFeatureConfig.exposeC1Apps; | ||||||
|
||||||
private readonly integrationTabsInitialState: IntegrationsView = { | ||||||
[IntegrationView.ACCOUNTING]: false, | ||||||
[IntegrationView.HRMS]: false, | ||||||
|
@@ -72,6 +77,19 @@ export class LandingV2Component implements OnInit { | |||||
[AccountingIntegrationApp.XERO]: '/integrations/xero' | ||||||
}; | ||||||
|
||||||
private readonly tpaNameToIntegrationKeyMap: Record<string, IntegrationAppKey> = { | ||||||
'Fyle Netsuite Integration': 'NETSUITE', | ||||||
'Fyle Sage Intacct Integration': 'INTACCT', | ||||||
'Fyle Quickbooks Integration': 'QBO', | ||||||
'Fyle Xero Integration': 'XERO', | ||||||
'Fyle Quickbooks Desktop (IIF) Integration': 'QBD', | ||||||
'Fyle Quickbooks Desktop Integration': 'QBD_DIRECT', | ||||||
'Fyle Sage 300 Integration': 'SAGE300', | ||||||
'Fyle Business Central Integration': 'BUSINESS_CENTRAL', | ||||||
'Fyle TravelPerk Integration': 'TRAVELPERK', | ||||||
'Fyle BambooHR Integration': 'BAMBOO_HR' | ||||||
}; | ||||||
|
||||||
readonly brandingConfig = brandingConfig; | ||||||
|
||||||
readonly isINCluster = this.storageService.get('cluster-domain')?.includes('in1'); | ||||||
|
@@ -100,7 +118,8 @@ export class LandingV2Component implements OnInit { | |||||
private router: Router, | ||||||
private siAuthService: SiAuthService, | ||||||
private storageService: StorageService, | ||||||
private orgService: OrgService | ||||||
private orgService: OrgService, | ||||||
private integrationService: IntegrationsService | ||||||
) { } | ||||||
|
||||||
|
||||||
|
@@ -146,6 +165,10 @@ export class LandingV2Component implements OnInit { | |||||
return false; | ||||||
} | ||||||
|
||||||
isAppConnected(appKey: IntegrationAppKey) { | ||||||
return this.connectedApps?.includes(appKey); | ||||||
} | ||||||
|
||||||
openAccountingIntegrationApp(accountingIntegrationApp: AccountingIntegrationApp): void { | ||||||
|
||||||
// For local dev, we perform auth via loginWithRefreshToken on Fyle login redirect (/auth/redirect) | ||||||
|
@@ -215,7 +238,17 @@ export class LandingV2Component implements OnInit { | |||||
}); | ||||||
} | ||||||
|
||||||
private storeConnectedApps() { | ||||||
this.integrationService.getIntegrations().subscribe(integrations => { | ||||||
const tpaNames = integrations.map(integration => integration.tpa_name); | ||||||
const connectedApps = tpaNames.map(tpaName => this.tpaNameToIntegrationKeyMap[tpaName]); | ||||||
|
||||||
this.connectedApps = connectedApps; | ||||||
}); | ||||||
} | ||||||
Comment on lines
+241
to
+248
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling and cleanup subscription. The subscription to + private destroy$ = new Subject<void>();
private storeConnectedApps() {
- this.integrationService.getIntegrations().subscribe(integrations => {
+ this.integrationService.getIntegrations().pipe(
+ takeUntil(this.destroy$),
+ catchError(error => {
+ console.error('Failed to fetch integrations:', error);
+ return of([]);
+ })
+ ).subscribe(integrations => {
const tpaNames = integrations.map(integration => integration.tpa_name);
const connectedApps = tpaNames.map(tpaName => this.tpaNameToIntegrationKeyMap[tpaName]);
this.connectedApps = connectedApps;
});
} Don't forget to implement ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
} |
||||||
|
||||||
ngOnInit(): void { | ||||||
this.setupLoginWatcher(); | ||||||
this.storeConnectedApps(); | ||||||
} | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enable tests and add coverage for getIntegrations method.
The test suite has the following issues:
xdescribe
getIntegrations
methodHere's how to improve the test coverage: