Skip to content

Commit 5524d4d

Browse files
authored
feat: add magic mail in setting page (#4049)
* feat: add magic mail in setting page * fix: minor * fix: comments and flaky tests
1 parent 48205fb commit 5524d4d

File tree

9 files changed

+164
-47
lines changed

9 files changed

+164
-47
lines changed

src/app/core/models/info-card-data.model.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ export interface InfoCardData {
44
contentToCopy: string;
55
toastMessageContent: string;
66
isShown: boolean;
7+
showMagicEmail?: boolean;
8+
showBetaTag?: boolean;
79
}
Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,30 @@
11
<div class="info-card__card" (click)="copyToClipboard(contentToCopy)">
2-
<div class="info-card__card__title">{{ title }}</div>
2+
<div class="info-card__card__title-container">
3+
<div class="info-card__card__title">{{ title }}</div>
4+
@if (showBetaTag()) {
5+
<div class="info-card__card__beta-tag">{{ 'infoCard.beta' | transloco }}</div>
6+
}
7+
</div>
38
<div class="info-card__content-container">
49
<div class="info-card__content-container__content">{{ content }}</div>
5-
<div class="info-card__content-container__icon-container">
6-
<ion-icon
7-
class="info-card__content-container__icon-container__icon"
8-
src="../../../../assets/svg/duplicate.svg"
9-
></ion-icon>
10-
</div>
10+
@if (!showEmail()) {
11+
<div class="info-card__content-container__icon-container">
12+
<ion-icon
13+
class="info-card__content-container__icon-container__icon"
14+
src="../../../../assets/svg/duplicate.svg"
15+
></ion-icon>
16+
</div>
17+
}
1118
</div>
19+
@if (contentToCopy && showEmail()) {
20+
<div class="info-card__email-container">
21+
<div class="info-card__email-container__email">{{ contentToCopy }}</div>
22+
<div class="info-card__email-container__icon-container">
23+
<ion-icon
24+
class="info-card__email-container__icon-container__icon"
25+
src="../../../../assets/svg/duplicate.svg"
26+
></ion-icon>
27+
</div>
28+
</div>
29+
}
1230
</div>

src/app/fyle/my-profile/info-card/info-card.component.scss

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,40 @@
22

33
.info-card {
44
&__card {
5-
align-items: center;
6-
padding: 20px 0;
5+
align-items: flex-start;
6+
padding: 0;
7+
8+
&__title-container {
9+
display: flex;
10+
align-items: center;
11+
gap: 8px;
12+
flex-wrap: wrap;
13+
}
714

815
&__title {
916
font-size: 16px;
1017
font-weight: 500;
1118
color: colors.$pure-black;
1219
}
20+
21+
&__beta-tag {
22+
background-color: colors.$pure-black;
23+
color: colors.$pure-white;
24+
font-size: 12px;
25+
font-weight: 500;
26+
padding: 2px 8px;
27+
border-radius: 12px;
28+
}
1329
}
1430

1531
&__content-container {
1632
display: flex;
1733
align-items: center;
1834
justify-content: space-between;
19-
padding-top: 8px;
35+
padding-top: 6px;
2036

2137
&__content {
22-
font-size: 12px;
38+
font-size: 14px;
2339
color: colors.$black-light;
2440
}
2541

@@ -33,4 +49,28 @@
3349
}
3450
}
3551
}
52+
53+
&__email-container {
54+
display: flex;
55+
align-items: center;
56+
justify-content: space-between;
57+
margin-top: 6px;
58+
59+
&__email {
60+
font-size: 14px;
61+
font-weight: 500;
62+
color: colors.$gray-25;
63+
}
64+
65+
&__icon-container {
66+
padding-left: 8px;
67+
flex-shrink: 0;
68+
69+
&__icon {
70+
height: 24px;
71+
width: 24px;
72+
color: colors.$brand-primary;
73+
}
74+
}
75+
}
3676
}

src/app/fyle/my-profile/info-card/info-card.component.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import { Component, Input, inject, output } from '@angular/core';
1+
import { Component, Input, inject, input, output } from '@angular/core';
22
import { ClipboardService } from 'src/app/core/services/clipboard.service';
33
import { IonIcon } from '@ionic/angular/standalone';
4-
4+
import { TranslocoPipe } from '@jsverse/transloco';
55

66
@Component({
77
selector: 'app-info-card',
88
templateUrl: './info-card.component.html',
99
styleUrls: ['./info-card.component.scss'],
10-
imports: [
11-
IonIcon
12-
],
10+
imports: [IonIcon, TranslocoPipe],
1311
})
1412
export class InfoCardComponent {
1513
private clipboardService = inject(ClipboardService);
@@ -30,6 +28,10 @@ export class InfoCardComponent {
3028
// Your application code writes to the input. This prevents migration.
3129
@Input() toastMessageContent: string;
3230

31+
readonly showEmail = input<boolean>(false);
32+
33+
readonly showBetaTag = input<boolean>(false);
34+
3335
readonly copiedText = output<string>();
3436

3537
async copyToClipboard(contentToCopy: string) {

src/app/fyle/my-profile/my-profile.page.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070
[content]="infoCardData.content"
7171
[contentToCopy]="infoCardData.contentToCopy"
7272
[toastMessageContent]="infoCardData.toastMessageContent"
73+
[showEmail]="infoCardData.showMagicEmail"
74+
[showBetaTag]="infoCardData.showBetaTag"
7375
(copiedText)="showToastMessage($event, 'success')"
7476
></app-info-card>
7577
</div>

src/app/fyle/my-profile/my-profile.page.scss

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -141,17 +141,14 @@
141141
}
142142

143143
&__info-card-container {
144-
background-color: colors.$pure-white;
145-
border-radius: 8px;
146144
margin-bottom: 24px;
147145

148146
&__card {
149-
margin: 0 16px;
150-
border-bottom: 1px solid colors.$grey;
151-
152-
&--last {
153-
border-bottom: none;
154-
}
147+
background-color: colors.$pure-white;
148+
border-radius: 8px;
149+
padding: 16px;
150+
margin-bottom: 24px;
151+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
155152
}
156153
}
157154

src/app/fyle/my-profile/my-profile.page.spec.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core';
2-
import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing';
2+
import { ComponentFixture, TestBed, fakeAsync, tick, flush, waitForAsync } from '@angular/core/testing';
33
import { MatSnackBar } from '@angular/material/snack-bar';
44
import { ActivatedRoute } from '@angular/router';
55
import { RouterTestingModule } from '@angular/router/testing';
@@ -43,6 +43,8 @@ import { WalkthroughService } from 'src/app/core/services/walkthrough.service';
4343
import { FeatureConfig } from 'src/app/core/models/feature-config.model';
4444
import { Component } from '@angular/core';
4545
import { EmployeeDetailsCardComponent } from './employee-details-card/employee-details-card.component';
46+
import { TranslocoService } from '@jsverse/transloco';
47+
import { TranslocoModule } from '@jsverse/transloco';
4648

4749
// mock EmployeeDetailsCardComponent
4850
@Component({
@@ -123,9 +125,11 @@ describe('MyProfilePage', () => {
123125
'getIsOverlayClicked',
124126
'getProfileEmailOptInWalkthroughConfig',
125127
]);
128+
const translocoServiceSpy = jasmine.createSpyObj('TranslocoService', ['translate']);
129+
translocoServiceSpy.translate.and.returnValue('');
126130

127131
TestBed.configureTestingModule({
128-
imports: [RouterTestingModule, MyProfilePage],
132+
imports: [RouterTestingModule, MyProfilePage, TranslocoModule],
129133
providers: [
130134
{
131135
provide: ActivatedRoute,
@@ -229,6 +233,10 @@ describe('MyProfilePage', () => {
229233
provide: WalkthroughService,
230234
useValue: walkthroughServiceSpy,
231235
},
236+
{
237+
provide: TranslocoService,
238+
useValue: translocoServiceSpy,
239+
},
232240
SpenderService,
233241
provideHttpClient(withInterceptorsFromDi()),
234242
provideHttpClientTesting(),
@@ -393,10 +401,7 @@ describe('MyProfilePage', () => {
393401
...successToastProperties,
394402
panelClass: 'msb-success',
395403
});
396-
expect(snackbarProperties.setSnackbarProperties).toHaveBeenCalledOnceWith(
397-
'success',
398-
{ message }
399-
);
404+
expect(snackbarProperties.setSnackbarProperties).toHaveBeenCalledOnceWith('success', { message });
400405
expect(trackingService.showToastMessage).toHaveBeenCalledOnceWith({
401406
ToastContent: message,
402407
});
@@ -458,7 +463,7 @@ describe('MyProfilePage', () => {
458463
expect(platformEmployeeSettingsService.get).toHaveBeenCalledTimes(1);
459464
expect(orgService.getCurrentOrg).toHaveBeenCalledTimes(1);
460465
expect(orgSettingsService.get).toHaveBeenCalledTimes(1);
461-
expect(component.setInfoCardsData).toHaveBeenCalledTimes(1);
466+
expect(component.setInfoCardsData).toHaveBeenCalledTimes(2);
462467
expect(component.setPreferenceSettings).toHaveBeenCalledTimes(1);
463468
expect(component.setCCCFlags).toHaveBeenCalledTimes(1);
464469

@@ -742,7 +747,10 @@ describe('MyProfilePage', () => {
742747

743748
it('setInfoCardsData(): should show only email card for non USD orgs', () => {
744749
component.setInfoCardsData();
745-
expect(component.infoCardsData).toEqual(allInfoCardsData);
750+
expect(component.infoCardsData.length).toBe(1);
751+
expect(component.infoCardsData[0].contentToCopy).toBe('[email protected]');
752+
expect(component.infoCardsData[0].showMagicEmail).toBeTrue();
753+
expect(component.infoCardsData[0].isShown).toBeTrue();
746754
});
747755

748756
describe('toggleSetting():', () => {
@@ -1042,7 +1050,8 @@ describe('MyProfilePage', () => {
10421050
employeesService.getCommuteDetails.and.returnValue(of(commuteDetailsResponseData));
10431051

10441052
component.setCommuteDetails();
1045-
tick(100);
1053+
tick();
1054+
flush();
10461055

10471056
expect(authService.getEou).toHaveBeenCalledTimes(1);
10481057
expect(employeesService.getCommuteDetails).toHaveBeenCalledOnceWith(apiEouRes);
@@ -1068,7 +1077,8 @@ describe('MyProfilePage', () => {
10681077
employeesService.getCommuteDetails.and.returnValue(of(commuteDetailsWithMiles));
10691078

10701079
component.setCommuteDetails();
1071-
tick(100);
1080+
tick();
1081+
flush();
10721082

10731083
expect(authService.getEou).toHaveBeenCalledTimes(1);
10741084
expect(employeesService.getCommuteDetails).toHaveBeenCalledOnceWith(apiEouRes);

src/app/fyle/my-profile/my-profile.page.ts

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
import { Component, EventEmitter, inject } from '@angular/core';
22
import { MatSnackBar } from '@angular/material/snack-bar';
33
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
4-
import { IonButtons, IonContent, IonHeader, IonIcon, IonSkeletonText, IonTitle, IonToolbar, ModalController, PopoverController } from '@ionic/angular/standalone';
4+
import {
5+
IonButtons,
6+
IonContent,
7+
IonHeader,
8+
IonIcon,
9+
IonSkeletonText,
10+
IonTitle,
11+
IonToolbar,
12+
ModalController,
13+
PopoverController,
14+
} from '@ionic/angular/standalone';
515
import { Observable, Subscription, concat, forkJoin, from, noop, finalize } from 'rxjs';
6-
import { map, shareReplay, switchMap } from 'rxjs/operators';
16+
import { map, shareReplay, switchMap, take } from 'rxjs/operators';
717
import { ExtendedOrgUser } from 'src/app/core/models/extended-org-user.model';
818
import { InfoCardData } from 'src/app/core/models/info-card-data.model';
919
import { Org } from 'src/app/core/models/org.model';
@@ -57,6 +67,7 @@ import { MatRipple } from '@angular/material/core';
5767
import { NgClass, AsyncPipe } from '@angular/common';
5868
import { InfoCardComponent } from './info-card/info-card.component';
5969
import { PreferenceSettingComponent } from './preference-setting/preference-setting.component';
70+
import { TranslocoService } from '@jsverse/transloco';
6071

6172
@Component({
6273
selector: 'app-my-profile',
@@ -79,7 +90,7 @@ import { PreferenceSettingComponent } from './preference-setting/preference-sett
7990
NgClass,
8091
PreferenceSettingComponent,
8192
ProfileOptInCardComponent,
82-
RouterLink
93+
RouterLink,
8394
],
8495
})
8596
export class MyProfilePage {
@@ -137,6 +148,8 @@ export class MyProfilePage {
137148

138149
private featureConfigService = inject(FeatureConfigService);
139150

151+
private translocoService = inject(TranslocoService);
152+
140153
employeeSettings: EmployeeSettings;
141154

142155
orgSettings: OrgSettings;
@@ -160,7 +173,7 @@ export class MyProfilePage {
160173

161174
preferenceSettings: PreferenceSetting[] = [];
162175

163-
infoCardsData: CopyCardDetails[];
176+
infoCardsData: InfoCardData[];
164177

165178
commuteDetails: CommuteDetails;
166179

@@ -375,8 +388,14 @@ export class MyProfilePage {
375388
this.org$ = this.orgService.getCurrentOrg();
376389
const orgSettings$ = this.orgSettingsService.get();
377390

391+
// Set info cards initially (without eou for email receipts card)
378392
this.setInfoCardsData();
379393

394+
// Subscribe to eou$ to update info cards when eou is available (for Magic mail card)
395+
this.eou$.pipe(take(1)).subscribe((eou) => {
396+
this.setInfoCardsData(eou);
397+
});
398+
380399
forkJoin({
381400
employeeSettings: employeeSettings$,
382401
orgSettings: orgSettings$,
@@ -456,18 +475,31 @@ export class MyProfilePage {
456475
this.preferenceSettings = allPreferenceSettings.filter((setting) => setting.isAllowed);
457476
}
458477

459-
setInfoCardsData(): void {
478+
setInfoCardsData(eou?: ExtendedOrgUser): void {
460479
const fyleEmail = '[email protected]';
461480

462-
const allInfoCardsData: InfoCardData[] = [
463-
{
464-
title: 'Email receipts',
465-
content: `Forward your receipts to Sage Expense Management at ${fyleEmail}.`,
466-
contentToCopy: fyleEmail,
467-
toastMessageContent: 'Email copied successfully',
481+
const allInfoCardsData: InfoCardData[] = [];
482+
483+
if (eou?.ou?.special_email) {
484+
allInfoCardsData.push({
485+
title: this.translocoService.translate<string>('myProfile.magicMail.title'),
486+
content: this.translocoService.translate<string>('myProfile.magicMail.content'),
487+
contentToCopy: eou.ou.special_email,
488+
toastMessageContent: this.translocoService.translate<string>('myProfile.emailCopiedSuccessfully'),
468489
isShown: true,
469-
},
470-
];
490+
showMagicEmail: true,
491+
showBetaTag: true,
492+
});
493+
}
494+
495+
allInfoCardsData.push({
496+
title: this.translocoService.translate<string>('myProfile.emailReceipts.title'),
497+
content: this.translocoService.translate<string>('myProfile.emailReceipts.content'),
498+
contentToCopy: fyleEmail,
499+
toastMessageContent: this.translocoService.translate<string>('myProfile.emailCopiedSuccessfully'),
500+
isShown: true,
501+
showMagicEmail: true,
502+
});
471503

472504
this.infoCardsData = allInfoCardsData.filter((infoCardData) => infoCardData.isShown);
473505
}

0 commit comments

Comments
 (0)