Skip to content

Commit c212e03

Browse files
authored
Merge pull request #20050 from mozilla/fxa-13016-otp-email
feat(email): add templates for passwordless
2 parents 323dbf4 + a52a433 commit c212e03

16 files changed

Lines changed: 1189 additions & 7 deletions

File tree

libs/accounts/email-renderer/src/renderer/__snapshots__/fxa-email-renderer.spec.ts.snap

Lines changed: 818 additions & 0 deletions
Large diffs are not rendered by default.

libs/accounts/email-renderer/src/renderer/fxa-email-renderer.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,34 @@ describe('FxA Email Renderer', () => {
198198
expect(email.html).toMatchSnapshot('matches full email snapshot');
199199
});
200200

201+
it('should render renderPasswordlessSigninOtp', async () => {
202+
const email = await renderer.renderPasswordlessSigninOtp({
203+
code: '96318398',
204+
codeExpiryMinutes: 10,
205+
date: 'Jan 1, 2024',
206+
time: '12:00 PM',
207+
device: mockDevice,
208+
location: mockLocation,
209+
...defaultLayoutTemplateValues,
210+
});
211+
expect(email).toBeDefined();
212+
expect(email.html).toMatchSnapshot('matches full email snapshot');
213+
});
214+
215+
it('should render renderPasswordlessSignupOtp', async () => {
216+
const email = await renderer.renderPasswordlessSignupOtp({
217+
code: '96318398',
218+
codeExpiryMinutes: 10,
219+
date: 'Jan 1, 2024',
220+
time: '12:00 PM',
221+
device: mockDevice,
222+
location: mockLocation,
223+
...defaultLayoutTemplateValues,
224+
});
225+
expect(email).toBeDefined();
226+
expect(email.html).toMatchSnapshot('matches full email snapshot');
227+
});
228+
201229
it('should render renderPasswordReset', async () => {
202230
const email = await renderer.renderPasswordReset({
203231
date: 'Jan 1, 2024',

libs/accounts/email-renderer/src/renderer/fxa-email-renderer.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import * as NewDeviceLogin from '../templates/newDeviceLogin';
1616
import * as PasswordChanged from '../templates/passwordChanged';
1717
import * as PasswordChangeRequired from '../templates/passwordChangeRequired';
1818
import * as PasswordForgotOtp from '../templates/passwordForgotOtp';
19+
import * as PasswordlessSigninOtp from '../templates/passwordlessSigninOtp';
20+
import * as PasswordlessSignupOtp from '../templates/passwordlessSignupOtp';
1921
import * as PasswordReset from '../templates/passwordReset';
2022
import * as PasswordResetAccountRecovery from '../templates/passwordResetAccountRecovery';
2123
import * as PasswordResetRecoveryPhone from '../templates/passwordResetRecoveryPhone';
@@ -199,6 +201,30 @@ export class FxaEmailRenderer extends EmailRenderer {
199201
});
200202
}
201203

204+
async renderPasswordlessSigninOtp(
205+
opts: WithFxaLayouts<PasswordlessSigninOtp.TemplateData>
206+
) {
207+
return this.renderEmail({
208+
template: PasswordlessSigninOtp.template,
209+
version: PasswordlessSigninOtp.version,
210+
layout: PasswordlessSigninOtp.layout,
211+
includes: PasswordlessSigninOtp.includes,
212+
...opts,
213+
});
214+
}
215+
216+
async renderPasswordlessSignupOtp(
217+
opts: WithFxaLayouts<PasswordlessSignupOtp.TemplateData>
218+
) {
219+
return this.renderEmail({
220+
template: PasswordlessSignupOtp.template,
221+
version: PasswordlessSignupOtp.version,
222+
layout: PasswordlessSignupOtp.layout,
223+
includes: PasswordlessSignupOtp.includes,
224+
...opts,
225+
});
226+
}
227+
202228
async renderPasswordReset(opts: WithFxaLayouts<PasswordReset.TemplateData>) {
203229
return this.renderEmail({
204230
template: PasswordReset.template,

libs/accounts/email-renderer/src/templates/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export * as passwordReset from './passwordReset';
1414
export * as passwordChanged from './passwordChanged';
1515
export * as passwordChangeRequired from './passwordChangeRequired';
1616
export * as passwordForgotOtp from './passwordForgotOtp';
17+
export * as passwordlessSigninOtp from './passwordlessSigninOtp';
18+
export * as passwordlessSignupOtp from './passwordlessSignupOtp';
1719
export * as passwordResetAccountRecovery from './passwordResetAccountRecovery';
1820
export * as passwordResetRecoveryPhone from './passwordResetRecoveryPhone';
1921
export * as passwordResetWithRecoveryKeyPrompt from './passwordResetWithRecoveryKeyPrompt';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Variables:
2+
# $code (String) - The confirmation code for sign-in
3+
# $codeExpiryMinutes (Number) - Number of minutes until the code expires
4+
passwordless-signin-otp-subject = Use { $code } to finish sign in
5+
passwordless-signin-otp-preview = Code expires in { $codeExpiryMinutes } minutes
6+
passwordless-signin-otp-title = Finish your sign in
7+
passwordless-signin-otp-request = This email was used to sign in from:
8+
passwordless-signin-otp-code = Use this confirmation code:
9+
passwordless-signin-otp-expiry-notice = It expires in { $codeExpiryMinutes } minutes.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<%# This Source Code Form is subject to the terms of the Mozilla Public #
2+
License, v. 2.0. If a copy of the MPL was not distributed with this # file, You
3+
can obtain one at http://mozilla.org/MPL/2.0/. %>
4+
5+
<mj-section>
6+
<mj-column>
7+
<mj-text css-class="text-header">
8+
<span data-l10n-id="passwordless-signin-otp-title">Finish your sign in</span>
9+
</mj-text>
10+
11+
<mj-text css-class="text-body">
12+
<span data-l10n-id="passwordless-signin-otp-request">
13+
This email was used to sign in from:
14+
</span>
15+
</mj-text>
16+
</mj-column>
17+
</mj-section>
18+
19+
<%- include('/partials/userInfo/index.mjml') %>
20+
21+
<mj-section>
22+
<mj-column>
23+
24+
<mj-text css-class="text-body">
25+
<span data-l10n-id="passwordless-signin-otp-code">
26+
Use this confirmation code:
27+
</span>
28+
</mj-text>
29+
30+
<mj-text css-class="code-large"><%- code %></mj-text>
31+
32+
<mj-text css-class="text-body-no-margin">
33+
<span data-l10n-id="passwordless-signin-otp-expiry-notice" data-l10n-args='{"codeExpiryMinutes": <%- codeExpiryMinutes %>}'>It expires in <%- codeExpiryMinutes %> minutes.</span>
34+
</mj-text>
35+
</mj-column>
36+
</mj-section>
37+
38+
<mj-section>
39+
<mj-column>
40+
<mj-text css-class="text-body-subtext">
41+
<%- include('/partials/automatedEmailNoAction/index.mjml') %>
42+
</mj-text>
43+
</mj-column>
44+
</mj-section>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
import { Meta } from '@storybook/html';
6+
import { MOCK_USER_INFO } from '../../partials/userInfo/mocks';
7+
import { storyWithProps } from '../../storybook-email';
8+
import { includes, TemplateData } from './index';
9+
10+
export default {
11+
title: 'FxA Emails/Templates/passwordlessSigninOtp',
12+
} as Meta;
13+
14+
const data = {
15+
...MOCK_USER_INFO,
16+
code: '96318398',
17+
codeExpiryMinutes: 10,
18+
supportUrl: 'https://support.mozilla.org',
19+
};
20+
21+
const createStory = storyWithProps<TemplateData>(
22+
'passwordlessSigninOtp',
23+
'OTP sent to user for passwordless sign-in.',
24+
data,
25+
includes
26+
);
27+
28+
export const PasswordlessSigninOtp = createStory();
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
import { TemplateData as AutomatedEmailNoActionTemplateData } from '../../partials/automatedEmailNoAction';
6+
import { TemplateData as UserInfoTemplateData } from '../../partials/userInfo';
7+
8+
export type TemplateData = AutomatedEmailNoActionTemplateData &
9+
UserInfoTemplateData & {
10+
code: string;
11+
codeExpiryMinutes: number;
12+
time: string;
13+
date: string;
14+
};
15+
16+
export const template = 'passwordlessSigninOtp';
17+
export const version = 1;
18+
export const layout = 'fxa';
19+
export const includes = {
20+
subject: {
21+
id: 'passwordless-signin-otp-subject',
22+
message: 'Use <%- code %> to finish sign in',
23+
},
24+
preview: {
25+
id: 'passwordless-signin-otp-preview',
26+
message: 'Code expires in <%- codeExpiryMinutes %> minutes',
27+
},
28+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
passwordless-signin-otp-title = "Finish your sign in"
2+
3+
passwordless-signin-otp-request = "This email was used to sign in from:"
4+
5+
<%- include('/partials/userInfo/index.txt') %>
6+
7+
passwordless-signin-otp-code = "Use this confirmation code:"
8+
9+
<%- code %>
10+
11+
passwordless-signin-otp-expiry-notice = "It expires in <%- codeExpiryMinutes %> minutes."
12+
13+
<%- include('/partials/automatedEmailNoAction/index.txt') %>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Variables:
2+
# $code (String) - The confirmation code for sign-up
3+
# $codeExpiryMinutes (Number) - Number of minutes until the code expires
4+
passwordless-signup-otp-subject = Use { $code } to finish sign up
5+
passwordless-signup-otp-preview = Code expires in { $codeExpiryMinutes } minutes
6+
passwordless-signup-otp-title = Finish your sign up
7+
passwordless-signup-otp-request = An account was created using this email address from:
8+
passwordless-signup-otp-code = Use this confirmation code:
9+
passwordless-signup-otp-expiry-notice = It expires in { $codeExpiryMinutes } minutes.

0 commit comments

Comments
 (0)