@remit/web-client 0.0.299 → 0.0.301
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/package.json +2 -1
- package/src/auth/amplify-config.ts +2 -3
- package/src/auth/cognito-sign-in.stories.tsx +90 -0
- package/src/auth/email-sign-in.stories.tsx +128 -0
- package/src/components/mail/MailSidebarAdapter.tsx +2 -0
- package/src/components/onboarding/onboarding.stories.tsx +460 -0
- package/src/components/settings/AccountServices.render.test.ts +254 -0
- package/src/components/settings/AccountServices.tsx +181 -0
- package/src/components/settings/AccountServicesCard.stories.tsx +71 -0
- package/src/components/settings/AccountServicesCard.tsx +76 -0
- package/src/components/settings/accounts.stories.tsx +363 -0
- package/src/components/settings/advanced.stories.tsx +162 -0
- package/src/components/settings/appearance.stories.tsx +33 -0
- package/src/components/settings/folders.stories.tsx +89 -0
- package/src/components/settings/self-update.stories.tsx +270 -0
- package/src/components/settings/senders.stories.tsx +155 -0
- package/src/lib/folder-failure.test.ts +29 -0
- package/src/lib/folder-failure.ts +10 -0
- package/src/mocks/story-frame/AppStory.tsx +21 -6
- package/src/mocks/story-frame/mail-handlers.ts +69 -7
- package/src/mocks/story-frame/mail-world.ts +22 -4
- package/src/mocks/story-frame/onboarding-handlers.ts +97 -0
- package/src/mocks/story-frame/session.tsx +19 -0
- package/src/routes/settings/-accounts.test.ts +1 -0
- package/src/routes/settings/accounts.tsx +28 -20
- package/src/routes/settings/folders.tsx +2 -0
- package/src/test-support/fixtures.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/web-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.301",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Remit web client, published as composable primitives — the app shell, auth shells, and runtime config. A distributor imports what it composes and bundles it.",
|
|
6
6
|
"exports": {
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
"@remit/api-http-client": "*",
|
|
78
78
|
"@remit/data-ports": "*",
|
|
79
79
|
"@remit/domain-enums": "*",
|
|
80
|
+
"@remit/mail-oauth-service": "*",
|
|
80
81
|
"@remit/ui": "*",
|
|
81
82
|
"@tanstack/react-query": "^5",
|
|
82
83
|
"@tanstack/react-query-devtools": "^5",
|
|
@@ -24,11 +24,10 @@ const readCognitoEnv = (): CognitoEnv | null => {
|
|
|
24
24
|
};
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
export const isCognitoConfigured = (): boolean => cognitoEnv !== null;
|
|
27
|
+
export const isCognitoConfigured = (): boolean => readCognitoEnv() !== null;
|
|
30
28
|
|
|
31
29
|
export const configureAmplify = (): void => {
|
|
30
|
+
const cognitoEnv = readCognitoEnv();
|
|
32
31
|
if (!cognitoEnv) {
|
|
33
32
|
console.warn(
|
|
34
33
|
"[auth] Cognito user pool is not configured. Running in local dev mode without Cognito auth.",
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect, userEvent, within } from "storybook/test";
|
|
3
|
+
import { cognitoAuthProvider } from "@/auth/cognito-provider";
|
|
4
|
+
import { AppStory } from "@/mocks/story-frame/AppStory";
|
|
5
|
+
import { mailHandlers } from "@/mocks/story-frame/mail-handlers";
|
|
6
|
+
import { mailWorld } from "@/mocks/story-frame/mail-world";
|
|
7
|
+
import { withRuntimeConfig } from "@/mocks/story-frame/session";
|
|
8
|
+
|
|
9
|
+
const userPool = withRuntimeConfig({
|
|
10
|
+
cognito: {
|
|
11
|
+
userPoolId: "eu-west-1_Story0001",
|
|
12
|
+
clientId: "story-client-id",
|
|
13
|
+
region: "eu-west-1",
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const meta = {
|
|
18
|
+
title: "Playground/Shipped/Sign in/Cognito",
|
|
19
|
+
component: AppStory,
|
|
20
|
+
args: { url: "/mail/brief", authProvider: cognitoAuthProvider },
|
|
21
|
+
parameters: {
|
|
22
|
+
layout: "fullscreen",
|
|
23
|
+
theme: "dark",
|
|
24
|
+
msw: { handlers: mailHandlers(mailWorld()) },
|
|
25
|
+
},
|
|
26
|
+
beforeEach: userPool,
|
|
27
|
+
} satisfies Meta<typeof AppStory>;
|
|
28
|
+
export default meta;
|
|
29
|
+
|
|
30
|
+
type Story = StoryObj<typeof meta>;
|
|
31
|
+
|
|
32
|
+
const phone = { viewport: { value: "mobile", isRotated: false } };
|
|
33
|
+
|
|
34
|
+
const signInForm = async (canvasElement: HTMLElement) => {
|
|
35
|
+
const canvas = within(canvasElement);
|
|
36
|
+
await expect(
|
|
37
|
+
await canvas.findByRole("tab", { name: "Sign In", selected: true }),
|
|
38
|
+
).toBeVisible();
|
|
39
|
+
await expect(canvas.getByLabelText("Username")).toHaveAttribute(
|
|
40
|
+
"placeholder",
|
|
41
|
+
"Enter your username",
|
|
42
|
+
);
|
|
43
|
+
await expect(canvas.getByLabelText("Password")).toHaveAttribute(
|
|
44
|
+
"placeholder",
|
|
45
|
+
"Enter your password",
|
|
46
|
+
);
|
|
47
|
+
await expect(canvas.getByRole("button", { name: "Sign in" })).toBeVisible();
|
|
48
|
+
await expect(canvas.queryByText("Q3 planning")).toBeNull();
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const SignInMobile: Story = {
|
|
52
|
+
name: "Sign In — mobile",
|
|
53
|
+
globals: phone,
|
|
54
|
+
play: async ({ canvasElement }) => signInForm(canvasElement),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const CreateAccountMobile: Story = {
|
|
58
|
+
name: "Create Account — mobile",
|
|
59
|
+
globals: phone,
|
|
60
|
+
play: async ({ canvasElement }) => {
|
|
61
|
+
const canvas = within(canvasElement);
|
|
62
|
+
await userEvent.click(
|
|
63
|
+
await canvas.findByRole("tab", { name: "Create Account" }),
|
|
64
|
+
);
|
|
65
|
+
await expect(
|
|
66
|
+
await canvas.findByPlaceholderText("Confirm your password"),
|
|
67
|
+
).toBeVisible();
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const SignInDesktop: Story = {
|
|
72
|
+
name: "Sign In — desktop",
|
|
73
|
+
play: async ({ canvasElement }) => signInForm(canvasElement),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const SignInLight: Story = {
|
|
77
|
+
name: "Sign In — light",
|
|
78
|
+
parameters: { theme: "light" },
|
|
79
|
+
play: async ({ canvasElement }) => signInForm(canvasElement),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const LocalDevBanner: Story = {
|
|
83
|
+
name: "Local dev banner",
|
|
84
|
+
beforeEach: withRuntimeConfig({}),
|
|
85
|
+
play: async ({ canvasElement }) => {
|
|
86
|
+
const canvas = within(canvasElement);
|
|
87
|
+
await expect(await canvas.findByText("Local dev")).toBeVisible();
|
|
88
|
+
await expect(await canvas.findByText("Q3 planning")).toBeVisible();
|
|
89
|
+
},
|
|
90
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { HttpResponse, http } from "msw";
|
|
3
|
+
import { expect, userEvent, within } from "storybook/test";
|
|
4
|
+
import { betterAuthProvider } from "@/auth/better-auth-provider";
|
|
5
|
+
import { AppStory } from "@/mocks/story-frame/AppStory";
|
|
6
|
+
import { mailHandlers } from "@/mocks/story-frame/mail-handlers";
|
|
7
|
+
import { mailWorld } from "@/mocks/story-frame/mail-world";
|
|
8
|
+
import { withRuntimeConfig } from "@/mocks/story-frame/session";
|
|
9
|
+
|
|
10
|
+
const signedOut = http.get("/api/auth/get-session", () =>
|
|
11
|
+
HttpResponse.json(null),
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const meta = {
|
|
15
|
+
title: "Playground/Shipped/Sign in/Email and password",
|
|
16
|
+
component: AppStory,
|
|
17
|
+
args: { url: "/mail/brief", authProvider: betterAuthProvider },
|
|
18
|
+
parameters: {
|
|
19
|
+
layout: "fullscreen",
|
|
20
|
+
msw: { handlers: [signedOut, ...mailHandlers(mailWorld())] },
|
|
21
|
+
},
|
|
22
|
+
beforeEach: withRuntimeConfig({ betterAuthEnabled: true }),
|
|
23
|
+
} satisfies Meta<typeof AppStory>;
|
|
24
|
+
export default meta;
|
|
25
|
+
|
|
26
|
+
type Story = StoryObj<typeof meta>;
|
|
27
|
+
|
|
28
|
+
const submit = (canvasElement: HTMLElement): HTMLElement => {
|
|
29
|
+
const button = canvasElement.querySelector<HTMLElement>(
|
|
30
|
+
'button[type="submit"]',
|
|
31
|
+
);
|
|
32
|
+
if (!button) throw new Error("the sign-in form has no submit button");
|
|
33
|
+
return button;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const fill = async (canvasElement: HTMLElement) => {
|
|
37
|
+
const canvas = within(canvasElement);
|
|
38
|
+
await userEvent.type(
|
|
39
|
+
await canvas.findByLabelText("Email"),
|
|
40
|
+
"alice@example.com",
|
|
41
|
+
);
|
|
42
|
+
await userEvent.type(canvas.getByLabelText("Password"), "not-my-password");
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const SignIn: Story = {
|
|
46
|
+
play: async ({ canvasElement }) => {
|
|
47
|
+
const canvas = within(canvasElement);
|
|
48
|
+
await expect(
|
|
49
|
+
await canvas.findByRole("button", { name: "Sign in", pressed: true }),
|
|
50
|
+
).toBeVisible();
|
|
51
|
+
await expect(canvas.getByLabelText("Email")).toBeVisible();
|
|
52
|
+
await expect(canvas.queryByLabelText("Name")).toBeNull();
|
|
53
|
+
await expect(canvas.queryByText("Q3 planning")).toBeNull();
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const CreateAccount: Story = {
|
|
58
|
+
play: async ({ canvasElement }) => {
|
|
59
|
+
const canvas = within(canvasElement);
|
|
60
|
+
await userEvent.click(
|
|
61
|
+
await canvas.findByRole("button", { name: "Create account" }),
|
|
62
|
+
);
|
|
63
|
+
await expect(await canvas.findByLabelText("Name")).toBeVisible();
|
|
64
|
+
await expect(
|
|
65
|
+
canvas.getByRole("button", { name: "Create account", pressed: true }),
|
|
66
|
+
).toBeVisible();
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const WrongPassword: Story = {
|
|
71
|
+
parameters: {
|
|
72
|
+
msw: {
|
|
73
|
+
handlers: [
|
|
74
|
+
signedOut,
|
|
75
|
+
http.post("/api/auth/sign-in/email", () =>
|
|
76
|
+
HttpResponse.json(
|
|
77
|
+
{
|
|
78
|
+
code: "INVALID_EMAIL_OR_PASSWORD",
|
|
79
|
+
message: "Invalid email or password",
|
|
80
|
+
},
|
|
81
|
+
{ status: 401 },
|
|
82
|
+
),
|
|
83
|
+
),
|
|
84
|
+
...mailHandlers(mailWorld()),
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
play: async ({ canvasElement }) => {
|
|
89
|
+
const canvas = within(canvasElement);
|
|
90
|
+
await fill(canvasElement);
|
|
91
|
+
await userEvent.click(submit(canvasElement));
|
|
92
|
+
await expect(
|
|
93
|
+
await canvas.findByText("Invalid email or password"),
|
|
94
|
+
).toBeVisible();
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const SignupsClosed: Story = {
|
|
99
|
+
parameters: {
|
|
100
|
+
msw: {
|
|
101
|
+
handlers: [
|
|
102
|
+
signedOut,
|
|
103
|
+
http.post("/api/auth/sign-up/email", () =>
|
|
104
|
+
HttpResponse.json(
|
|
105
|
+
{
|
|
106
|
+
code: "EMAIL_PASSWORD_SIGN_UP_DISABLED",
|
|
107
|
+
message: "Email and password sign up is not enabled",
|
|
108
|
+
},
|
|
109
|
+
{ status: 400 },
|
|
110
|
+
),
|
|
111
|
+
),
|
|
112
|
+
...mailHandlers(mailWorld()),
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
play: async ({ canvasElement }) => {
|
|
117
|
+
const canvas = within(canvasElement);
|
|
118
|
+
await userEvent.click(
|
|
119
|
+
await canvas.findByRole("button", { name: "Create account" }),
|
|
120
|
+
);
|
|
121
|
+
await userEvent.type(await canvas.findByLabelText("Name"), "Alice");
|
|
122
|
+
await fill(canvasElement);
|
|
123
|
+
await userEvent.click(submit(canvasElement));
|
|
124
|
+
await expect(
|
|
125
|
+
await canvas.findByText(/New signups are currently closed/),
|
|
126
|
+
).toBeVisible();
|
|
127
|
+
},
|
|
128
|
+
};
|
|
@@ -12,6 +12,7 @@ import { NavSidebar } from "@remit/ui";
|
|
|
12
12
|
import { useQueries, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
13
13
|
import { useCallback, useMemo } from "react";
|
|
14
14
|
import { useTranslation } from "react-i18next";
|
|
15
|
+
import { folderFailure } from "@/lib/folder-failure";
|
|
15
16
|
import {
|
|
16
17
|
buildMailboxRoleMap,
|
|
17
18
|
labelForMailbox,
|
|
@@ -103,6 +104,7 @@ function toNavMailbox(
|
|
|
103
104
|
unseen: showBadge ? mb.unseenCount : undefined,
|
|
104
105
|
role,
|
|
105
106
|
fullPath: mb.fullPath,
|
|
107
|
+
failure: folderFailure(mb),
|
|
106
108
|
};
|
|
107
109
|
}
|
|
108
110
|
|