@homefile/components-v2 2.63.0 → 2.63.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/onboarding/CardFooter.js +2 -1
- package/dist/components/onboarding/pages/ActivateAccount.js +3 -3
- package/dist/components/onboarding/pages/AuthHeader.js +2 -2
- package/dist/components/onboarding/pages/BasePageWrapper.d.ts +32 -1
- package/dist/components/onboarding/pages/BasePageWrapper.js +23 -3
- package/dist/components/onboarding/pages/EmailValidation.js +2 -2
- package/dist/components/onboarding/pages/MessagePage.js +3 -3
- package/dist/components/onboarding/pages/NewPassword.js +3 -3
- package/dist/components/onboarding/pages/ResendResetPassword.js +2 -2
- package/dist/components/onboarding/pages/ResetPassword.js +3 -3
- package/dist/components/onboarding/pages/SignIn.js +3 -3
- package/dist/components/onboarding/pages/SignUp.js +3 -3
- package/dist/components/onboarding/pages/TwoFactor.d.ts +1 -1
- package/dist/components/onboarding/pages/TwoFactor.js +13 -4
- package/dist/components/onboarding/pages/Welcome.js +2 -2
- package/dist/interfaces/pages/TwoFactor.interface.d.ts +2 -1
- package/dist/stories/onboarding/CardFooter.stories.d.ts +2 -1
- package/dist/stories/onboarding/CardFooter.stories.js +24 -0
- package/dist/stories/onboarding/pages/ActivateAccount.stories.d.ts +3 -1
- package/dist/stories/onboarding/pages/ActivateAccount.stories.js +41 -0
- package/dist/stories/onboarding/pages/EmailValidation.stories.d.ts +3 -1
- package/dist/stories/onboarding/pages/EmailValidation.stories.js +43 -0
- package/dist/stories/onboarding/pages/MessagePage.stories.js +10 -2
- package/dist/stories/onboarding/pages/NewPassword.stories.d.ts +3 -1
- package/dist/stories/onboarding/pages/NewPassword.stories.js +44 -0
- package/dist/stories/onboarding/pages/ResendResetPassword.stories.d.ts +3 -1
- package/dist/stories/onboarding/pages/ResendResetPassword.stories.js +47 -0
- package/dist/stories/onboarding/pages/ResetPassword.stories.d.ts +3 -1
- package/dist/stories/onboarding/pages/ResetPassword.stories.js +45 -0
- package/dist/stories/onboarding/pages/SignIn.stories.d.ts +2 -0
- package/dist/stories/onboarding/pages/SignIn.stories.js +52 -0
- package/dist/stories/onboarding/pages/SignUp.stories.d.ts +2 -1
- package/dist/stories/onboarding/pages/SignUp.stories.js +34 -0
- package/dist/stories/onboarding/pages/TwoFactor.stories.d.ts +3 -1
- package/dist/stories/onboarding/pages/TwoFactor.stories.js +68 -11
- package/dist/stories/onboarding/pages/Welcome.stories.d.ts +2 -1
- package/dist/stories/onboarding/pages/Welcome.stories.js +29 -0
- package/dist/stories/onboarding/phoneLayout.d.ts +20 -0
- package/dist/stories/onboarding/phoneLayout.js +89 -0
- package/package.json +1 -1
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
+
import { fn, within } from '@storybook/test';
|
|
2
12
|
import { NewPassword, CloudsAnimation } from '../../../components';
|
|
13
|
+
import { PHONE, SMALL_TABLET, expectCardAtTop, expectCardBelowTopGap, expectCardFullWidth, expectPageFillsScreen, expectSides, getCard, } from '../phoneLayout';
|
|
3
14
|
export default {
|
|
4
15
|
title: 'Pages/Onboarding',
|
|
5
16
|
component: NewPassword,
|
|
@@ -28,3 +39,36 @@ NewPasswordNoAnswer.args = {
|
|
|
28
39
|
errorMessage: "We couldn't reach Homefile. Check your connection, then press Reset Password.",
|
|
29
40
|
supportEmail: 'support@homefile.com',
|
|
30
41
|
};
|
|
42
|
+
// New password on a phone: the card starts at the top, spans the screen and
|
|
43
|
+
// runs down to the copyright at the bottom of the screen, like sign in; the
|
|
44
|
+
// fields and the button keep 13px from its sides.
|
|
45
|
+
export const NewPasswordPhone = {
|
|
46
|
+
parameters: PHONE,
|
|
47
|
+
args: {
|
|
48
|
+
isLoading: false,
|
|
49
|
+
userEmail: 'jordan@example.com',
|
|
50
|
+
handleReset: fn(),
|
|
51
|
+
},
|
|
52
|
+
render: (args) => (_jsx(CloudsAnimation, { children: _jsx(NewPassword, Object.assign({}, args)) })),
|
|
53
|
+
play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
54
|
+
const canvas = within(canvasElement);
|
|
55
|
+
const card = getCard(canvasElement);
|
|
56
|
+
yield expectCardAtTop(card);
|
|
57
|
+
yield expectCardFullWidth(card);
|
|
58
|
+
yield expectSides(card, {
|
|
59
|
+
'password field': canvasElement.querySelector('input#password'),
|
|
60
|
+
'confirm field': canvasElement.querySelector('input#confirmPassword'),
|
|
61
|
+
'Reset Password button': canvas.getByRole('button', { name: 'Reset Password' }),
|
|
62
|
+
});
|
|
63
|
+
yield expectPageFillsScreen(card);
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
// New password on a small tablet (744px): the card keeps the top gap.
|
|
67
|
+
export const NewPasswordSmallTablet = {
|
|
68
|
+
parameters: SMALL_TABLET,
|
|
69
|
+
args: NewPasswordPhone.args,
|
|
70
|
+
render: NewPasswordPhone.render,
|
|
71
|
+
play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
72
|
+
yield expectCardBelowTopGap(getCard(canvasElement));
|
|
73
|
+
}),
|
|
74
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Meta } from '@storybook/react';
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
import { EmailValidationI } from '../../../interfaces';
|
|
3
3
|
declare const _default: Meta<EmailValidationI>;
|
|
4
4
|
export default _default;
|
|
@@ -9,3 +9,5 @@ export declare const ResendResetPasswordPage: {
|
|
|
9
9
|
isLoading: boolean;
|
|
10
10
|
};
|
|
11
11
|
};
|
|
12
|
+
export declare const ResendResetPasswordPhone: StoryObj<EmailValidationI>;
|
|
13
|
+
export declare const ResendResetPasswordSmallTablet: StoryObj<EmailValidationI>;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
+
import { fn, within } from '@storybook/test';
|
|
2
12
|
import { ResendResetPassword, CloudsAnimation } from '../../../components';
|
|
13
|
+
import { PHONE, SMALL_TABLET, expectCardAtTop, expectCardBelowTopGap, expectCardFullWidth, expectContentSides, expectPageFillsScreen, expectSides, getCard, } from '../phoneLayout';
|
|
3
14
|
export default {
|
|
4
15
|
title: 'Pages/Onboarding',
|
|
5
16
|
component: ResendResetPassword,
|
|
@@ -11,3 +22,39 @@ ResendResetPasswordPage.args = {
|
|
|
11
22
|
email: 'user@user.com.br',
|
|
12
23
|
isLoading: true,
|
|
13
24
|
};
|
|
25
|
+
// The reset-email-sent page on a phone: like sign in, the card starts at the
|
|
26
|
+
// top and runs down to the bottom of the screen; its title and buttons keep
|
|
27
|
+
// 13px from the sides.
|
|
28
|
+
export const ResendResetPasswordPhone = {
|
|
29
|
+
parameters: PHONE,
|
|
30
|
+
args: {
|
|
31
|
+
email: 'jordan@example.com',
|
|
32
|
+
isLoading: false,
|
|
33
|
+
handleResendBt: fn(),
|
|
34
|
+
handleBack: fn(),
|
|
35
|
+
},
|
|
36
|
+
render: (args) => (_jsx(CloudsAnimation, { children: _jsx(ResendResetPassword, Object.assign({}, args)) })),
|
|
37
|
+
play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
38
|
+
const canvas = within(canvasElement);
|
|
39
|
+
const card = getCard(canvasElement);
|
|
40
|
+
yield expectCardAtTop(card);
|
|
41
|
+
yield expectCardFullWidth(card);
|
|
42
|
+
yield expectContentSides(card, {
|
|
43
|
+
title: canvas.getByText('jordan@example.com').parentElement,
|
|
44
|
+
});
|
|
45
|
+
yield expectSides(card, {
|
|
46
|
+
'Resend button': canvas.getByRole('button', { name: 'Resend Reset Password Link' }),
|
|
47
|
+
'Back to Log In button': canvas.getByRole('button', { name: 'Back to Log In' }),
|
|
48
|
+
});
|
|
49
|
+
yield expectPageFillsScreen(card);
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
// The reset-email-sent page on a small tablet (744px): the card keeps the top gap.
|
|
53
|
+
export const ResendResetPasswordSmallTablet = {
|
|
54
|
+
parameters: SMALL_TABLET,
|
|
55
|
+
args: ResendResetPasswordPhone.args,
|
|
56
|
+
render: ResendResetPasswordPhone.render,
|
|
57
|
+
play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
58
|
+
yield expectCardBelowTopGap(getCard(canvasElement));
|
|
59
|
+
}),
|
|
60
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Meta } from '@storybook/react';
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
import { ResetPasswordI } from '../../../interfaces';
|
|
3
3
|
declare const _default: Meta<ResetPasswordI>;
|
|
4
4
|
export default _default;
|
|
@@ -9,3 +9,5 @@ export declare const ResetPasswordPage: {
|
|
|
9
9
|
resetError: boolean;
|
|
10
10
|
};
|
|
11
11
|
};
|
|
12
|
+
export declare const ResetPasswordPhone: StoryObj<ResetPasswordI>;
|
|
13
|
+
export declare const ResetPasswordSmallTablet: StoryObj<ResetPasswordI>;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
+
import { fn, within } from '@storybook/test';
|
|
2
12
|
import { ResetPassword, CloudsAnimation } from '../../../components';
|
|
13
|
+
import { PHONE, SMALL_TABLET, expectCardAtTop, expectCardBelowTopGap, expectCardFullWidth, expectPageFillsScreen, expectSides, getCard, } from '../phoneLayout';
|
|
3
14
|
export default {
|
|
4
15
|
title: 'Pages/Onboarding',
|
|
5
16
|
component: ResetPassword,
|
|
@@ -11,3 +22,37 @@ ResetPasswordPage.args = {
|
|
|
11
22
|
isLoading: false,
|
|
12
23
|
resetError: false,
|
|
13
24
|
};
|
|
25
|
+
// Reset password on a phone: the card starts at the top, spans the screen and
|
|
26
|
+
// runs down to the copyright at the bottom of the screen, like sign in; the
|
|
27
|
+
// field, the button and the Log-in box keep 13px from its sides.
|
|
28
|
+
export const ResetPasswordPhone = {
|
|
29
|
+
parameters: PHONE,
|
|
30
|
+
args: {
|
|
31
|
+
isLoading: false,
|
|
32
|
+
resetError: false,
|
|
33
|
+
handleReset: fn(),
|
|
34
|
+
handleSignIn: fn(),
|
|
35
|
+
},
|
|
36
|
+
render: (args) => (_jsx(CloudsAnimation, { children: _jsx(ResetPassword, Object.assign({}, args)) })),
|
|
37
|
+
play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
38
|
+
const canvas = within(canvasElement);
|
|
39
|
+
const card = getCard(canvasElement);
|
|
40
|
+
yield expectCardAtTop(card);
|
|
41
|
+
yield expectCardFullWidth(card);
|
|
42
|
+
yield expectSides(card, {
|
|
43
|
+
'email field': canvasElement.querySelector('input#email'),
|
|
44
|
+
'Reset Password button': canvas.getByRole('button', { name: 'Reset Password' }),
|
|
45
|
+
'Log-in box': canvas.getByRole('button', { name: 'Log in' }).parentElement,
|
|
46
|
+
});
|
|
47
|
+
yield expectPageFillsScreen(card);
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
// Reset password on a small tablet (744px): the card keeps the top gap.
|
|
51
|
+
export const ResetPasswordSmallTablet = {
|
|
52
|
+
parameters: SMALL_TABLET,
|
|
53
|
+
args: ResetPasswordPhone.args,
|
|
54
|
+
render: ResetPasswordPhone.render,
|
|
55
|
+
play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
56
|
+
yield expectCardBelowTopGap(getCard(canvasElement));
|
|
57
|
+
}),
|
|
58
|
+
};
|
|
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
11
|
import { expect, fn, within } from '@storybook/test';
|
|
12
12
|
import { SignIn, CloudsAnimation } from '../../../components';
|
|
13
|
+
import { PHONE, SMALL_TABLET, expectCardAtTop, expectCardFullWidth, expectNoCardShadow, expectSides, getCard, } from '../phoneLayout';
|
|
13
14
|
export default {
|
|
14
15
|
title: 'Pages/Onboarding',
|
|
15
16
|
component: SignIn,
|
|
@@ -38,6 +39,7 @@ const CONNECT_HEADING = 'Connect (512) 555-0147';
|
|
|
38
39
|
const CONNECT_LINE = 'Log in to your Homefile account to connect this phone. Then Homi will know it’s you.';
|
|
39
40
|
// The page a texter reaches from Homi's link to connect their phone.
|
|
40
41
|
export const SignInConnect = {
|
|
42
|
+
parameters: PHONE,
|
|
41
43
|
args: {
|
|
42
44
|
isLoading: false,
|
|
43
45
|
heading: CONNECT_HEADING,
|
|
@@ -56,5 +58,55 @@ export const SignInConnect = {
|
|
|
56
58
|
yield expect(canvas.queryByText('Don’t have a Homefile account?')).toBeNull();
|
|
57
59
|
yield expect(canvas.queryByRole('button', { name: 'Sign up' })).toBeNull();
|
|
58
60
|
yield expect(canvas.queryByRole('button', { name: 'Continue with Apple' })).toBeNull();
|
|
61
|
+
// On a phone: full screen, no bar on top, 13px sides.
|
|
62
|
+
const card = getCard(canvasElement);
|
|
63
|
+
yield expectCardAtTop(card);
|
|
64
|
+
yield expectCardFullWidth(card);
|
|
65
|
+
yield expectSides(card, {
|
|
66
|
+
'Google button': canvas.getByRole('button', { name: 'Continue with Google' }),
|
|
67
|
+
'email field': canvasElement.querySelector('input#email'),
|
|
68
|
+
'password field': canvasElement.querySelector('input#password'),
|
|
69
|
+
'Log in button': canvas.getByRole('button', { name: 'Log in' }),
|
|
70
|
+
});
|
|
71
|
+
}),
|
|
72
|
+
};
|
|
73
|
+
// The partner sign-in on a phone: the partner header, the form and the Sign-up
|
|
74
|
+
// box all keep 13px from the sides of the screen.
|
|
75
|
+
export const SignInPartnerPhone = {
|
|
76
|
+
parameters: PHONE,
|
|
77
|
+
args: {
|
|
78
|
+
isPartner: true,
|
|
79
|
+
isLoading: false,
|
|
80
|
+
handleForgotPassword: fn(),
|
|
81
|
+
handleSignIn: fn(),
|
|
82
|
+
handleSignUp: fn(),
|
|
83
|
+
},
|
|
84
|
+
play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
85
|
+
const canvas = within(canvasElement);
|
|
86
|
+
const card = getCard(canvasElement);
|
|
87
|
+
yield expectCardAtTop(card);
|
|
88
|
+
yield expectCardFullWidth(card);
|
|
89
|
+
yield expectSides(card, {
|
|
90
|
+
'partner header': [
|
|
91
|
+
canvas.getByAltText('Homefile'),
|
|
92
|
+
canvas.getByText('Partner Admin'),
|
|
93
|
+
],
|
|
94
|
+
'email field': canvasElement.querySelector('input#email'),
|
|
95
|
+
'password field': canvasElement.querySelector('input#password'),
|
|
96
|
+
'Log in button': canvas.getByRole('button', { name: 'Log in' }),
|
|
97
|
+
'Sign-up box': canvas.getByText('Don’t have a Homefile Partner account?'),
|
|
98
|
+
});
|
|
99
|
+
}),
|
|
100
|
+
};
|
|
101
|
+
// Sign in on a small tablet (744px): still the full-screen page below 768px,
|
|
102
|
+
// as in 2.63.0 (Matt kept sign in as it is at every width, 2026-10-08).
|
|
103
|
+
export const SignInSmallTablet = {
|
|
104
|
+
parameters: SMALL_TABLET,
|
|
105
|
+
args: SignInConnect.args,
|
|
106
|
+
play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
107
|
+
const card = getCard(canvasElement);
|
|
108
|
+
yield expectCardAtTop(card);
|
|
109
|
+
yield expectCardFullWidth(card);
|
|
110
|
+
yield expectNoCardShadow(card);
|
|
59
111
|
}),
|
|
60
112
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Meta } from '@storybook/react';
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
import { SignUpI } from '../../../interfaces';
|
|
3
3
|
declare const _default: Meta<SignUpI>;
|
|
4
4
|
export default _default;
|
|
@@ -14,3 +14,4 @@ export declare const SignUpPage: {
|
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
16
|
};
|
|
17
|
+
export declare const SignUpPhone: StoryObj<SignUpI>;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
+
import { fn, within } from '@storybook/test';
|
|
2
12
|
import { CloudsAnimation, SignUp } from '../../../components';
|
|
13
|
+
import { PHONE, expectCardAtTop, expectCardFullWidth, expectSides, getCard, } from '../phoneLayout';
|
|
3
14
|
export default {
|
|
4
15
|
title: 'Pages/Onboarding',
|
|
5
16
|
component: SignUp,
|
|
@@ -20,3 +31,26 @@ SignUpPage.args = {
|
|
|
20
31
|
showPromoCode: true,
|
|
21
32
|
values: valuesFilled,
|
|
22
33
|
};
|
|
34
|
+
// Sign up on a phone: full screen, no bar on top, and the form and the Log-in
|
|
35
|
+
// box keep 13px from the sides of the screen.
|
|
36
|
+
export const SignUpPhone = {
|
|
37
|
+
parameters: PHONE,
|
|
38
|
+
args: {
|
|
39
|
+
isLoading: false,
|
|
40
|
+
handleCreateAccount: fn(),
|
|
41
|
+
handleSignIn: fn(),
|
|
42
|
+
},
|
|
43
|
+
play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
44
|
+
const canvas = within(canvasElement);
|
|
45
|
+
const card = getCard(canvasElement);
|
|
46
|
+
yield expectCardAtTop(card);
|
|
47
|
+
yield expectCardFullWidth(card);
|
|
48
|
+
yield expectSides(card, {
|
|
49
|
+
'name field': canvasElement.querySelector('input#name'),
|
|
50
|
+
'email field': canvasElement.querySelector('input#email'),
|
|
51
|
+
'password field': canvasElement.querySelector('input#password'),
|
|
52
|
+
'Create account button': canvas.getByRole('button', { name: 'Create account' }),
|
|
53
|
+
'Log-in box': canvas.getByText('Already have an account?'),
|
|
54
|
+
});
|
|
55
|
+
}),
|
|
56
|
+
};
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react';
|
|
1
|
+
import { Decorator, Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
import { TwoFactorI } from '../../../interfaces';
|
|
3
3
|
declare const _default: Meta<TwoFactorI>;
|
|
4
4
|
export default _default;
|
|
5
5
|
export declare const TwoFactorPage: {
|
|
6
6
|
(args: TwoFactorI): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
decorators: Decorator[];
|
|
7
8
|
play({ canvasElement }: {
|
|
8
9
|
canvasElement: HTMLElement;
|
|
9
10
|
}): Promise<void>;
|
|
10
11
|
};
|
|
11
12
|
export declare const TwoFactorConnect: StoryObj<TwoFactorI>;
|
|
12
13
|
export declare const TwoFactorConnectWrongCode: StoryObj<TwoFactorI>;
|
|
14
|
+
export declare const TwoFactorConnectResendLimit: StoryObj<TwoFactorI>;
|
|
@@ -11,6 +11,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
11
11
|
import { Box } from '@chakra-ui/react';
|
|
12
12
|
import { expect, fn, within } from '@storybook/test';
|
|
13
13
|
import { TwoFactor } from '../../../components';
|
|
14
|
+
import { PHONE, expectCardAtTop, expectCardFullWidth, expectSides, getCard, } from '../phoneLayout';
|
|
14
15
|
export default {
|
|
15
16
|
title: 'Pages/Onboarding',
|
|
16
17
|
component: TwoFactor,
|
|
@@ -22,13 +23,13 @@ export default {
|
|
|
22
23
|
onReset: (code) => alert(`Code submitted: ${code}`),
|
|
23
24
|
onResend: () => alert('Resend code clicked'),
|
|
24
25
|
},
|
|
25
|
-
decorators: [
|
|
26
|
-
(Story) => (_jsx(Box, { p: "base", children: _jsx(Story, {}) })),
|
|
27
|
-
],
|
|
28
26
|
};
|
|
29
27
|
export const TwoFactorPage = (args) => {
|
|
30
28
|
return _jsx(TwoFactor, Object.assign({}, args));
|
|
31
29
|
};
|
|
30
|
+
// The default page keeps its framed look; the connect stories render full-screen.
|
|
31
|
+
const framed = (Story) => (_jsx(Box, { p: "base", children: _jsx(Story, {}) }));
|
|
32
|
+
TwoFactorPage.decorators = [framed];
|
|
32
33
|
TwoFactorPage.play = (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
33
34
|
const canvas = within(canvasElement);
|
|
34
35
|
// The default page is unchanged: its own title, subtitle and button.
|
|
@@ -37,43 +38,99 @@ TwoFactorPage.play = (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvas
|
|
|
37
38
|
yield expect(canvas.getByRole('button', { name: 'Submit' })).toBeVisible();
|
|
38
39
|
});
|
|
39
40
|
const CONNECT_HEADING = 'Check your email';
|
|
40
|
-
const CONNECT_LINE = 'We sent a 6-digit code to j•••••@example.com. Enter it to connect (512) 555-0147.';
|
|
41
|
-
|
|
41
|
+
const CONNECT_LINE = 'We sent a 6-digit code to j•••••@example.com. Enter it to connect (512) 555-0147. Keep this page open while you get your code.';
|
|
42
|
+
// Matt, 2026-10-08: one calm line in normal type, in place of the bold warning.
|
|
43
|
+
const CONNECT_NOTE = 'Only enter the code if (512) 555-0147 is your phone.';
|
|
42
44
|
const WRONG_CODE = 'That code didn’t work. Check the email and try again.';
|
|
45
|
+
// Matt, 2026-10-08T03:00:39Z: a press of "Resend new code" past the two resends
|
|
46
|
+
// a link allows shows this line under the button; the button stays.
|
|
47
|
+
const RESEND_LIMIT = "You've asked for 2 new codes. Text Homi for a new link if this one doesn't arrive.";
|
|
48
|
+
// On a phone: full screen, no bar on top, 13px sides.
|
|
49
|
+
const expectPhoneLayout = (canvasElement) => __awaiter(void 0, void 0, void 0, function* () {
|
|
50
|
+
const canvas = within(canvasElement);
|
|
51
|
+
const card = getCard(canvasElement);
|
|
52
|
+
yield expectCardAtTop(card);
|
|
53
|
+
yield expectCardFullWidth(card);
|
|
54
|
+
yield expectSides(card, {
|
|
55
|
+
'code field': canvas.getByPlaceholderText('Enter your code here'),
|
|
56
|
+
'Connect phone button': canvas.getByRole('button', { name: 'Connect phone' }),
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
// The calm line is on every state of the screen: in normal type, above the code
|
|
60
|
+
// field, and on the screen without scrolling on a phone (Ken, v4.10 (d)).
|
|
61
|
+
const expectCalmNote = (canvasElement) => __awaiter(void 0, void 0, void 0, function* () {
|
|
62
|
+
const canvas = within(canvasElement);
|
|
63
|
+
const note = canvas.getByText(CONNECT_NOTE);
|
|
64
|
+
yield expect(note).toBeVisible();
|
|
65
|
+
yield expect(Number(getComputedStyle(note).fontWeight), 'note weight').toBe(400);
|
|
66
|
+
const noteBottom = note.getBoundingClientRect().bottom;
|
|
67
|
+
const codeField = canvas.getByPlaceholderText('Enter your code here');
|
|
68
|
+
yield expect(noteBottom, 'note above the code field').toBeLessThanOrEqual(codeField.getBoundingClientRect().top);
|
|
69
|
+
yield expect(noteBottom, 'note on screen').toBeLessThanOrEqual(window.innerHeight);
|
|
70
|
+
});
|
|
43
71
|
const connectArgs = {
|
|
44
72
|
twoFactorMethod: 'email',
|
|
45
73
|
heading: CONNECT_HEADING,
|
|
46
74
|
description: CONNECT_LINE,
|
|
47
|
-
|
|
75
|
+
note: CONNECT_NOTE,
|
|
48
76
|
submitLabel: 'Connect phone',
|
|
49
77
|
onReset: fn(),
|
|
50
78
|
onResend: fn(),
|
|
51
79
|
};
|
|
52
80
|
// The code step of the page a texter reaches from Homi's link.
|
|
53
81
|
export const TwoFactorConnect = {
|
|
82
|
+
parameters: PHONE,
|
|
54
83
|
args: connectArgs,
|
|
55
84
|
play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
56
85
|
const canvas = within(canvasElement);
|
|
57
86
|
yield expect(canvas.getByText(CONNECT_HEADING)).toBeVisible();
|
|
58
87
|
yield expect(canvas.getByText(CONNECT_LINE)).toBeVisible();
|
|
59
|
-
|
|
60
|
-
yield expect(warning).toBeVisible();
|
|
61
|
-
yield expect(Number(getComputedStyle(warning).fontWeight)).toBeGreaterThanOrEqual(600);
|
|
88
|
+
yield expectCalmNote(canvasElement);
|
|
62
89
|
yield expect(canvas.getByRole('button', { name: 'Connect phone' })).toBeVisible();
|
|
63
90
|
yield expect(canvas.getByRole('button', { name: 'Resend new code' })).toBeVisible();
|
|
64
91
|
yield expect(canvas.queryByText('Please validate your identity to log in.')).toBeNull();
|
|
65
92
|
yield expect(canvas.queryByRole('button', { name: 'Submit' })).toBeNull();
|
|
93
|
+
// No line under "Resend new code" unless the page passes one.
|
|
94
|
+
yield expect(canvas.queryByText(RESEND_LIMIT)).toBeNull();
|
|
95
|
+
yield expectPhoneLayout(canvasElement);
|
|
66
96
|
}),
|
|
67
97
|
};
|
|
68
|
-
// After a wrong code: the error sits above the code field, and the
|
|
98
|
+
// After a wrong code: the error sits above the code field, and the calm line stays.
|
|
69
99
|
export const TwoFactorConnectWrongCode = {
|
|
100
|
+
parameters: PHONE,
|
|
70
101
|
args: Object.assign(Object.assign({}, connectArgs), { errorMessage: WRONG_CODE }),
|
|
71
102
|
play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
72
103
|
const canvas = within(canvasElement);
|
|
73
104
|
const error = canvas.getByText(WRONG_CODE);
|
|
74
105
|
yield expect(error).toBeVisible();
|
|
75
|
-
yield
|
|
106
|
+
yield expectCalmNote(canvasElement);
|
|
76
107
|
const codeField = canvas.getByPlaceholderText('Enter your code here');
|
|
77
108
|
yield expect(error.getBoundingClientRect().bottom).toBeLessThanOrEqual(codeField.getBoundingClientRect().top);
|
|
109
|
+
yield expect(canvas.queryByText(RESEND_LIMIT)).toBeNull();
|
|
110
|
+
yield expectPhoneLayout(canvasElement);
|
|
111
|
+
}),
|
|
112
|
+
};
|
|
113
|
+
// A press past the resend limit: a calm line under "Resend new code", in normal
|
|
114
|
+
// type and without error styling; the button and the calm note stay.
|
|
115
|
+
const resendLimitArgs = Object.assign(Object.assign({}, connectArgs), { resendNote: RESEND_LIMIT });
|
|
116
|
+
export const TwoFactorConnectResendLimit = {
|
|
117
|
+
parameters: PHONE,
|
|
118
|
+
args: resendLimitArgs,
|
|
119
|
+
play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
120
|
+
const canvas = within(canvasElement);
|
|
121
|
+
const line = canvas.getByText(RESEND_LIMIT);
|
|
122
|
+
yield expect(line).toBeVisible();
|
|
123
|
+
const resend = canvas.getByRole('button', { name: 'Resend new code' });
|
|
124
|
+
yield expect(resend).toBeVisible();
|
|
125
|
+
yield expect(line.getBoundingClientRect().top, 'line under the resend button').toBeGreaterThanOrEqual(resend.getBoundingClientRect().bottom);
|
|
126
|
+
yield expect(line.getBoundingClientRect().bottom, 'line on screen').toBeLessThanOrEqual(window.innerHeight);
|
|
127
|
+
const style = getComputedStyle(line);
|
|
128
|
+
const note = getComputedStyle(canvas.getByText(CONNECT_NOTE));
|
|
129
|
+
yield expect(Number(style.fontWeight), 'line weight').toBe(400);
|
|
130
|
+
yield expect(style.color, 'line colour (no error styling)').toBe(note.color);
|
|
131
|
+
yield expect(line.closest('[role="alert"], .chakra-form__error-message')).toBeNull();
|
|
132
|
+
yield expect(line.children).toHaveLength(0);
|
|
133
|
+
yield expectCalmNote(canvasElement);
|
|
134
|
+
yield expectPhoneLayout(canvasElement);
|
|
78
135
|
}),
|
|
79
136
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Meta } from '@storybook/react';
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
import { WelcomePageI } from '../../../interfaces';
|
|
3
3
|
declare const _default: Meta<WelcomePageI>;
|
|
4
4
|
export default _default;
|
|
@@ -9,3 +9,4 @@ export declare const WelcomePage: {
|
|
|
9
9
|
videoUrl: string;
|
|
10
10
|
};
|
|
11
11
|
};
|
|
12
|
+
export declare const WelcomePhone: StoryObj<WelcomePageI>;
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
11
|
import { Box } from '@chakra-ui/react';
|
|
12
|
+
import { fn, within } from '@storybook/test';
|
|
3
13
|
import { Welcome } from '../../../components';
|
|
14
|
+
import { PHONE, expectCardFullWidth, expectSides, getCard } from '../phoneLayout';
|
|
4
15
|
export default {
|
|
5
16
|
title: 'Pages/Onboarding',
|
|
6
17
|
component: Welcome,
|
|
@@ -12,3 +23,21 @@ WelcomePage.args = {
|
|
|
12
23
|
name: 'Sam',
|
|
13
24
|
videoUrl: 'https://www.youtube.com/embed/rkt_YtZYRDI',
|
|
14
25
|
};
|
|
26
|
+
// The welcome page on a phone: the card spans the screen, and its text keeps
|
|
27
|
+
// 13px from the sides.
|
|
28
|
+
export const WelcomePhone = {
|
|
29
|
+
parameters: PHONE,
|
|
30
|
+
args: {
|
|
31
|
+
name: 'Jordan',
|
|
32
|
+
handleClick: fn(),
|
|
33
|
+
},
|
|
34
|
+
play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
|
|
35
|
+
const canvas = within(canvasElement);
|
|
36
|
+
const card = getCard(canvasElement);
|
|
37
|
+
yield expectCardFullWidth(card);
|
|
38
|
+
yield expectSides(card, {
|
|
39
|
+
message: canvas.getByText('We hope you enjoy your time with Homefile.'),
|
|
40
|
+
signature: canvas.getByText('Your Homefile Crew'),
|
|
41
|
+
});
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const PHONE: {
|
|
2
|
+
layout: string;
|
|
3
|
+
viewport: {
|
|
4
|
+
defaultViewport: string;
|
|
5
|
+
};
|
|
6
|
+
};
|
|
7
|
+
export declare const SMALL_TABLET: {
|
|
8
|
+
layout: string;
|
|
9
|
+
viewport: {
|
|
10
|
+
defaultViewport: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export declare const getCard: (canvasElement: HTMLElement) => HTMLElement;
|
|
14
|
+
export declare const expectCardFullWidth: (card: HTMLElement) => Promise<void>;
|
|
15
|
+
export declare const expectCardAtTop: (card: HTMLElement) => Promise<void>;
|
|
16
|
+
export declare const expectCardBelowTopGap: (card: HTMLElement) => Promise<void>;
|
|
17
|
+
export declare const expectNoCardShadow: (card: HTMLElement) => Promise<void>;
|
|
18
|
+
export declare const expectPageFillsScreen: (card: HTMLElement) => Promise<void>;
|
|
19
|
+
export declare const expectSides: (card: HTMLElement, elements: Record<string, Element | [Element, Element]>) => Promise<void>;
|
|
20
|
+
export declare const expectContentSides: (card: HTMLElement, elements: Record<string, HTMLElement>) => Promise<void>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { expect } from '@storybook/test';
|
|
11
|
+
// Onboarding pages on a phone (Matt, 2026-10-07): the card fills the screen
|
|
12
|
+
// from side to side, and its fields and buttons keep 13px from each side.
|
|
13
|
+
// The full-screen pages also start at the very top, with no bar above the card.
|
|
14
|
+
export const PHONE = {
|
|
15
|
+
layout: 'fullscreen',
|
|
16
|
+
viewport: { defaultViewport: 'iphone12' }, // 390x844
|
|
17
|
+
};
|
|
18
|
+
// A small tablet, between the 480px and 768px breakpoints (iPad mini, 744x1133).
|
|
19
|
+
export const SMALL_TABLET = {
|
|
20
|
+
layout: 'fullscreen',
|
|
21
|
+
viewport: { defaultViewport: 'ipadmini' },
|
|
22
|
+
};
|
|
23
|
+
const SIDE = 13;
|
|
24
|
+
// The onboarding top gap ('onboarding.top', 3.125rem).
|
|
25
|
+
const TOP_GAP = 50;
|
|
26
|
+
// The page's card: the first Chakra Container in the story.
|
|
27
|
+
export const getCard = (canvasElement) => canvasElement.querySelector('.chakra-container');
|
|
28
|
+
// The card spans the screen from side to side.
|
|
29
|
+
export const expectCardFullWidth = (card) => __awaiter(void 0, void 0, void 0, function* () {
|
|
30
|
+
const box = card.getBoundingClientRect();
|
|
31
|
+
yield expect(Math.round(box.left), 'card left').toBe(0);
|
|
32
|
+
yield expect(Math.round(box.right), 'card right').toBe(document.documentElement.clientWidth);
|
|
33
|
+
});
|
|
34
|
+
// The card starts at the very top of the screen: no bar above it.
|
|
35
|
+
export const expectCardAtTop = (card) => __awaiter(void 0, void 0, void 0, function* () {
|
|
36
|
+
yield expect(Math.round(card.getBoundingClientRect().top), 'card top').toBe(0);
|
|
37
|
+
});
|
|
38
|
+
// The card sits below the onboarding top gap, and is still a card: it has a
|
|
39
|
+
// shadow and is narrower than the screen (tablets and desktops).
|
|
40
|
+
export const expectCardBelowTopGap = (card) => __awaiter(void 0, void 0, void 0, function* () {
|
|
41
|
+
yield expect(Math.round(card.getBoundingClientRect().top), 'card top').toBe(TOP_GAP);
|
|
42
|
+
yield expect(getComputedStyle(card).boxShadow, 'card shadow').not.toBe('none');
|
|
43
|
+
yield expect(Math.round(card.getBoundingClientRect().width), 'card width').toBeLessThan(document.documentElement.clientWidth);
|
|
44
|
+
});
|
|
45
|
+
// The card has no shadow: on a phone it is the page, not a card on it.
|
|
46
|
+
export const expectNoCardShadow = (card) => __awaiter(void 0, void 0, void 0, function* () {
|
|
47
|
+
yield expect(getComputedStyle(card).boxShadow, 'card shadow').toBe('none');
|
|
48
|
+
});
|
|
49
|
+
// The page ends at the bottom of the screen, as sign in does on a phone: the
|
|
50
|
+
// card grows down to the copyright, and the copyright sits at the bottom of the
|
|
51
|
+
// screen. A page without the copyright: the card reaches the bottom.
|
|
52
|
+
export const expectPageFillsScreen = (card) => __awaiter(void 0, void 0, void 0, function* () {
|
|
53
|
+
const cardBottom = card.getBoundingClientRect().bottom;
|
|
54
|
+
const footer = card.nextElementSibling;
|
|
55
|
+
if (!footer) {
|
|
56
|
+
yield expect(Math.round(cardBottom), 'card bottom').toBe(window.innerHeight);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const box = footer.getBoundingClientRect();
|
|
60
|
+
const style = getComputedStyle(footer);
|
|
61
|
+
yield expect(Math.round(box.bottom + parseFloat(style.marginBottom)), 'page bottom').toBe(window.innerHeight);
|
|
62
|
+
yield expect(Math.round(cardBottom + parseFloat(style.marginTop)), 'card bottom').toBe(Math.round(box.top));
|
|
63
|
+
});
|
|
64
|
+
// Each element sits 13px in from both sides of the card. A row given as
|
|
65
|
+
// [first, last] is measured from its first element's left side to its last
|
|
66
|
+
// element's right side.
|
|
67
|
+
export const expectSides = (card, elements) => __awaiter(void 0, void 0, void 0, function* () {
|
|
68
|
+
const box = card.getBoundingClientRect();
|
|
69
|
+
for (const [name, element] of Object.entries(elements)) {
|
|
70
|
+
const [first, last] = Array.isArray(element) ? element : [element, element];
|
|
71
|
+
const left = first.getBoundingClientRect().left;
|
|
72
|
+
const right = last.getBoundingClientRect().right;
|
|
73
|
+
yield expect(Math.round(left - box.left), `${name} left`).toBe(SIDE);
|
|
74
|
+
yield expect(Math.round(box.right - right), `${name} right`).toBe(SIDE);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
// For a full-width block that sets the margin with its own padding (a centred
|
|
78
|
+
// title): its content starts 13px in from both sides of the card.
|
|
79
|
+
export const expectContentSides = (card, elements) => __awaiter(void 0, void 0, void 0, function* () {
|
|
80
|
+
const box = card.getBoundingClientRect();
|
|
81
|
+
for (const [name, element] of Object.entries(elements)) {
|
|
82
|
+
const rect = element.getBoundingClientRect();
|
|
83
|
+
const style = getComputedStyle(element);
|
|
84
|
+
const left = rect.left + parseFloat(style.borderLeftWidth) + parseFloat(style.paddingLeft);
|
|
85
|
+
const right = rect.right - parseFloat(style.borderRightWidth) - parseFloat(style.paddingRight);
|
|
86
|
+
yield expect(Math.round(left - box.left), `${name} left`).toBe(SIDE);
|
|
87
|
+
yield expect(Math.round(box.right - right), `${name} right`).toBe(SIDE);
|
|
88
|
+
}
|
|
89
|
+
});
|