From 7de5ce53bbe5e9e4bff6b0336d9a8b9212a41fae Mon Sep 17 00:00:00 2001 From: Arjun Date: Tue, 6 Aug 2024 15:37:31 +0530 Subject: [PATCH] feat: Added deep link redirection support for sms opt in (#3162) --- src/app/auth/switch-org/switch-org.page.ts | 34 +++++++++++ .../core/services/deep-link.service.spec.ts | 21 +++++++ src/app/core/services/deep-link.service.ts | 13 +++++ .../deep-link-redirection.page.spec.ts | 13 +++++ .../deep-link-redirection.page.ts | 57 +++++++++++++++++++ src/app/fyle/dashboard/dashboard.page.spec.ts | 13 +++++ src/app/fyle/dashboard/dashboard.page.ts | 45 ++++++++++++++- 7 files changed, 194 insertions(+), 2 deletions(-) diff --git a/src/app/auth/switch-org/switch-org.page.ts b/src/app/auth/switch-org/switch-org.page.ts index b1b976a485..5dbd9cc6d1 100644 --- a/src/app/auth/switch-org/switch-org.page.ts +++ b/src/app/auth/switch-org/switch-org.page.ts @@ -117,9 +117,12 @@ export class SwitchOrgPage implements OnInit, AfterViewChecked { (JSON.parse(that.activatedRoute.snapshot.params.invite_link as string) as boolean); const orgId = that.activatedRoute.snapshot.params.orgId as string; const txnId = this.activatedRoute.snapshot.params.txnId as string; + const openSMSOptInDialog = this.activatedRoute.snapshot.params.openSMSOptInDialog as string; if (orgId && txnId) { return this.redirectToExpensePage(orgId, txnId); + } else if (openSMSOptInDialog === 'true' && orgId) { + return this.redirectToDashboard(orgId); } else if (!choose) { from(that.loaderService.showLoader()) .pipe(switchMap(() => from(that.proceed(isFromInviteLink)))) @@ -133,6 +136,7 @@ export class SwitchOrgPage implements OnInit, AfterViewChecked { } }); } + this.activeOrg$ = this.orgService.getCurrentOrg(); this.primaryOrg$ = this.orgService.getPrimaryOrg(); @@ -187,6 +191,36 @@ export class SwitchOrgPage implements OnInit, AfterViewChecked { this.trackingService.showToastMessage({ ToastContent: toastMessageData.message }); } + redirectToDashboard(orgId: string): void { + from(this.loaderService.showLoader('Please wait...', 2000)) + .pipe( + switchMap(() => this.orgService.switchOrg(orgId)), + switchMap(() => { + globalCacheBusterNotifier.next(); + this.userEventService.clearTaskCache(); + this.recentLocalStorageItemsService.clearRecentLocalStorageCache(); + return from(this.authService.getEou()); + }), + map((eou) => { + this.setSentryUser(eou); + }), + finalize(() => this.loaderService.hideLoader()) + ) + .subscribe({ + next: () => { + this.router.navigate([ + '/', + 'enterprise', + 'my_dashboard', + { + openSMSOptInDialog: true, + }, + ]); + }, + error: () => this.router.navigate(['/', 'auth', 'switch_org']), + }); + } + redirectToExpensePage(orgId: string, txnId: string): void { from(this.loaderService.showLoader()) .pipe( diff --git a/src/app/core/services/deep-link.service.spec.ts b/src/app/core/services/deep-link.service.spec.ts index c5e10531bb..3a516fe6d1 100644 --- a/src/app/core/services/deep-link.service.spec.ts +++ b/src/app/core/services/deep-link.service.spec.ts @@ -163,6 +163,27 @@ describe('DeepLinkService', () => { ]); }); + it('should navigate to the dashboard when the URI contains "/my_dashboard" along with advance request ID', () => { + const orgId = 'oroX1Q9TTEO'; + const referrer = 'transactional_email'; + const openSMSOptInDialog = true; + + deepLinkService.redirect({ + redirect_uri: `${baseURL}/my_dashboard?org_id=${orgId}&open_sms_dialog=${openSMSOptInDialog}&referrer=${referrer}`, + }); + + expect(router.navigate).toHaveBeenCalledOnceWith([ + '/', + 'deep_link_redirection', + { + sub_module: 'my_dashboard', + openSMSOptInDialog, + orgId, + referrer, + }, + ]); + }); + it('should redirect to deep link redirection page with correct orgId and txnId', () => { deepLinkService.redirect({ redirect_uri: `${baseURL}/orOTDe765hQp/txMLI4Cc5zY5`, diff --git a/src/app/core/services/deep-link.service.ts b/src/app/core/services/deep-link.service.ts index d25dd1fe78..7c729f70e9 100644 --- a/src/app/core/services/deep-link.service.ts +++ b/src/app/core/services/deep-link.service.ts @@ -109,6 +109,19 @@ export class DeepLinkService { orgId, txnId, }); + } else if (redirectUri.match('my_dashboard')) { + // https://staging1.fyle.tech/app/main/#/my_dashboard?org_id=oroX1Q9TTEOg&open_sms_dialog=true&referrer=transactional_email + const referrer = redirectUri.match(/referrer=(\w+)/)?.[1]; + const orgId = redirectUri.match(/org_id=(\w+)/)?.[1]; + const openSMSOptInDialog = redirectUri.includes('open_sms_dialog=true'); + const properties = { + sub_module: 'my_dashboard', + openSMSOptInDialog, + orgId, + referrer, + }; + this.trackingService.smsDeepLinkOpened(properties); + this.router.navigate(['/', 'deep_link_redirection', properties]); } else { this.router.navigate(['/', 'auth', 'switch_org', { choose: true }]); } diff --git a/src/app/deep-link-redirection/deep-link-redirection.page.spec.ts b/src/app/deep-link-redirection/deep-link-redirection.page.spec.ts index 6fce1bcc41..55e14bceb8 100644 --- a/src/app/deep-link-redirection/deep-link-redirection.page.spec.ts +++ b/src/app/deep-link-redirection/deep-link-redirection.page.spec.ts @@ -410,6 +410,19 @@ describe('DeepLinkRedirectionPage', () => { tick(500); expect(component.redirectToAdvReqModule).toHaveBeenCalledTimes(1); })); + + it('should call redirectToDashboardModule() if the sub_module is my_dashboard', fakeAsync(() => { + activeroutemock.snapshot.params = { + sub_module: 'my_dashboard', + orgId: 'oroX1Q9TTEO', + referrer: 'transactional_email', + }; + spyOn(component, 'redirectToDashboardModule').and.stub(); + component.ionViewWillEnter(); + fixture.detectChanges(); + tick(500); + expect(component.redirectToDashboardModule).toHaveBeenCalledTimes(1); + })); }); it('should call switchOrg method of authService', () => { diff --git a/src/app/deep-link-redirection/deep-link-redirection.page.ts b/src/app/deep-link-redirection/deep-link-redirection.page.ts index b611db269e..1f44ce0d88 100644 --- a/src/app/deep-link-redirection/deep-link-redirection.page.ts +++ b/src/app/deep-link-redirection/deep-link-redirection.page.ts @@ -40,9 +40,66 @@ export class DeepLinkRedirectionPage { this.redirectToExpenseModule(); } else if (subModule === 'advReq') { this.redirectToAdvReqModule(); + } else if (subModule === 'my_dashboard') { + this.redirectToDashboardModule(); } } + async redirectToDashboardModule(): Promise { + const openSMSOptInDialog = this.activatedRoute.snapshot.params.openSMSOptInDialog as string; + const orgId = this.activatedRoute.snapshot.params.orgId as string; + + const eou$ = from(this.loaderService.showLoader('Loading....')).pipe( + switchMap(() => from(this.authService.getEou())), + catchError(() => { + this.switchOrg(); + return EMPTY; + }), + shareReplay(1) + ); + + // If orgId is the same as the current user orgId, then redirect to the dashboard page + eou$ + .pipe( + filter((eou) => orgId === eou.ou.org_id), + finalize(() => from(this.loaderService.hideLoader())) + ) + .subscribe({ + next: () => { + this.router.navigate([ + '/', + 'enterprise', + 'my_dashboard', + { + openSMSOptInDialog, + }, + ]); + }, + error: () => this.switchOrg(), + }); + + // If orgId is the diferent from the current user orgId, then redirect to switch org with orgId and openSMSOptInDialog + eou$ + .pipe( + filter((eou) => orgId !== eou.ou.org_id), + finalize(() => from(this.loaderService.hideLoader())) + ) + .subscribe({ + next: () => { + this.router.navigate([ + '/', + 'auth', + 'switch_org', + { + openSMSOptInDialog, + orgId, + }, + ]); + }, + error: () => this.switchOrg(), + }); + } + async redirectToAdvReqModule(): Promise { await this.loaderService.showLoader('Loading....'); const currentEou = await this.authService.getEou(); diff --git a/src/app/fyle/dashboard/dashboard.page.spec.ts b/src/app/fyle/dashboard/dashboard.page.spec.ts index 5f20e8590a..8b3da215da 100644 --- a/src/app/fyle/dashboard/dashboard.page.spec.ts +++ b/src/app/fyle/dashboard/dashboard.page.spec.ts @@ -34,6 +34,8 @@ import { AuthService } from 'src/app/core/services/auth.service'; import { apiEouRes } from 'src/app/core/mock-data/extended-org-user.data'; import { properties } from 'src/app/core/mock-data/modal-properties.data'; import { featureConfigOptInData } from 'src/app/core/mock-data/feature-config.data'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-properties.service'; describe('DashboardPage', () => { let component: DashboardPage; @@ -94,6 +96,8 @@ describe('DashboardPage', () => { const modalPropertiesSpy = jasmine.createSpyObj('ModalPropertiesService', ['getModalDefaultProperties']); const authServiceSpy = jasmine.createSpyObj('AuthService', ['getEou', 'refreshEou']); const modalControllerSpy = jasmine.createSpyObj('ModalController', ['create']); + const matSnackBarSpy = jasmine.createSpyObj('MatSnackBar', ['openFromComponent']); + const snackbarPropertiesSpy = jasmine.createSpyObj('SnackbarPropertiesService', ['setSnackbarProperties']); TestBed.configureTestingModule({ declarations: [DashboardPage], @@ -119,6 +123,7 @@ describe('DashboardPage', () => { queryParams: { state: 'tasks', }, + params: {}, }, }, }, @@ -127,6 +132,14 @@ describe('DashboardPage', () => { { provide: ModalPropertiesService, useValue: modalPropertiesSpy }, { provide: AuthService, useValue: authServiceSpy }, { provide: ModalController, useValue: modalControllerSpy }, + { + provide: MatSnackBar, + useValue: matSnackBarSpy, + }, + { + provide: SnackbarPropertiesService, + useValue: snackbarPropertiesSpy, + }, ], schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); diff --git a/src/app/fyle/dashboard/dashboard.page.ts b/src/app/fyle/dashboard/dashboard.page.ts index 0d7fa952e0..6e080ee86e 100644 --- a/src/app/fyle/dashboard/dashboard.page.ts +++ b/src/app/fyle/dashboard/dashboard.page.ts @@ -28,6 +28,10 @@ import { PromoteOptInModalComponent } from 'src/app/shared/components/promote-op import { AuthService } from 'src/app/core/services/auth.service'; import { ExtendedOrgUser } from 'src/app/core/models/extended-org-user.model'; import { DashboardState } from 'src/app/core/enums/dashboard-state.enum'; +import { FyOptInComponent } from 'src/app/shared/components/fy-opt-in/fy-opt-in.component'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { ToastMessageComponent } from 'src/app/shared/components/toast-message/toast-message.component'; +import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-properties.service'; @Component({ selector: 'app-dashboard', @@ -90,7 +94,9 @@ export class DashboardPage { private utilityService: UtilityService, private featureConfigService: FeatureConfigService, private modalProperties: ModalPropertiesService, - private authService: AuthService + private authService: AuthService, + private matSnackBar: MatSnackBar, + private snackbarProperties: SnackbarPropertiesService ) {} get displayedTaskCount(): number { @@ -153,6 +159,26 @@ export class DashboardPage { ); } + async openSMSOptInDialog(extendedOrgUser: ExtendedOrgUser): Promise { + const optInModal = await this.modalController.create({ + component: FyOptInComponent, + componentProps: { + extendedOrgUser, + }, + }); + + return await optInModal.present(); + } + + showInfoToastMessage(message: string): void { + const panelClass = 'msb-info'; + this.matSnackBar.openFromComponent(ToastMessageComponent, { + ...this.snackbarProperties.setSnackbarProperties('information', { message }), + panelClass, + }); + this.trackingService.showToastMessage({ ToastContent: message }); + } + ionViewWillEnter(): void { this.setupNetworkWatcher(); this.registerBackButtonAction(); @@ -170,11 +196,26 @@ export class DashboardPage { this.orgSettings$ = this.orgSettingsService.get().pipe(shareReplay(1)); this.specialCategories$ = this.categoriesService.getMileageOrPerDiemCategories().pipe(shareReplay(1)); this.homeCurrency$ = this.currencyService.getHomeCurrency().pipe(shareReplay(1)); - this.eou$ = from(this.authService.getEou()); + this.eou$ = from(this.authService.getEou()).pipe(shareReplay(1)); this.isUserFromINCluster$ = from(this.utilityService.isUserFromINCluster()); this.setShowOptInBanner(); + const openSMSOptInDialog = this.activatedRoute.snapshot.params.openSMSOptInDialog as string; + if (openSMSOptInDialog === 'true') { + this.eou$ + .pipe( + map((eou) => { + if (eou.ou.mobile_verified) { + this.showInfoToastMessage('You are already opted into text messaging!'); + } else { + this.openSMSOptInDialog(eou); + } + }) + ) + .subscribe(); + } + forkJoin({ orgSettings: this.orgSettings$, specialCategories: this.specialCategories$,