@remit/web-client 0.0.303 → 0.0.305
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 +1 -1
- package/src/components/compose/compose.stories.tsx +374 -0
- package/src/components/layout/ComposeFab.tsx +2 -15
- package/src/components/layout/mail-shell.stories.tsx +142 -0
- package/src/components/mail/detail-surface.stories.tsx +171 -0
- package/src/components/mail/mail-pickers.stories.tsx +82 -0
- package/src/components/mail/search.stories.tsx +280 -0
- package/src/components/mail/selection-wizard.stories.tsx +160 -0
- package/src/components/onboarding/OnboardingWizard.flow.test.ts +222 -2
- package/src/components/onboarding/OnboardingWizard.tsx +262 -15
- package/src/components/onboarding/onboarding.stories.tsx +90 -0
- package/src/components/settings/AccountServices.render.test.ts +4 -1
- package/src/components/settings/accounts.stories.tsx +5 -0
- package/src/mocks/story-frame/compose-world.ts +191 -0
- package/src/mocks/story-frame/mail-handlers.ts +195 -22
- package/src/mocks/story-frame/mail-world.ts +44 -18
- package/src/routes/settings/-accounts.test.ts +70 -2
- package/src/routes/settings/accounts.tsx +87 -7
|
@@ -26,11 +26,12 @@ export interface MailWorld {
|
|
|
26
26
|
vipSuggestions: RemitImapVipSuggestionEntry[];
|
|
27
27
|
addresses: RemitImapAddressResponse[];
|
|
28
28
|
systemUpdate: RemitImapSystemUpdateResponse;
|
|
29
|
+
bodies: Record<string, string>;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
const MINUTE = 60_000;
|
|
32
|
-
const HOUR = 60 * MINUTE;
|
|
33
|
-
const DAY = 24 * HOUR;
|
|
32
|
+
export const MINUTE = 60_000;
|
|
33
|
+
export const HOUR = 60 * MINUTE;
|
|
34
|
+
export const DAY = 24 * HOUR;
|
|
34
35
|
export const NOW = Date.UTC(2026, 8, 24, 13, 0);
|
|
35
36
|
|
|
36
37
|
const ROLES = [
|
|
@@ -93,8 +94,9 @@ const accounts = [
|
|
|
93
94
|
accountWithFolders(WORK, "alice.tan@acme.example", "Work"),
|
|
94
95
|
];
|
|
95
96
|
|
|
96
|
-
interface ThreadSeed {
|
|
97
|
+
export interface ThreadSeed {
|
|
97
98
|
id: string;
|
|
99
|
+
inThread?: string;
|
|
98
100
|
accountId: string;
|
|
99
101
|
role?: RemitImapCanonicalMailboxRole;
|
|
100
102
|
fromName: string;
|
|
@@ -108,10 +110,10 @@ interface ThreadSeed {
|
|
|
108
110
|
hasAttachment?: boolean;
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
const
|
|
113
|
+
export const threadRow = (seed: ThreadSeed): RemitImapThreadMessageResponse =>
|
|
112
114
|
makeThreadMessage({
|
|
113
115
|
messageId: `msg-${seed.id}`,
|
|
114
|
-
threadId: `thread-${seed.id}`,
|
|
116
|
+
threadId: `thread-${seed.inThread ?? seed.id}`,
|
|
115
117
|
threadMessageId: `tm-${seed.id}`,
|
|
116
118
|
accountId: seed.accountId,
|
|
117
119
|
mailboxId: mailboxIdFor(seed.accountId, seed.role ?? "Inbox"),
|
|
@@ -129,7 +131,7 @@ const thread = (seed: ThreadSeed): RemitImapThreadMessageResponse =>
|
|
|
129
131
|
});
|
|
130
132
|
|
|
131
133
|
const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
132
|
-
|
|
134
|
+
threadRow({
|
|
133
135
|
id: "q3-planning",
|
|
134
136
|
accountId: WORK,
|
|
135
137
|
fromName: "Ada Lovelace",
|
|
@@ -140,7 +142,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
140
142
|
ago: 20 * MINUTE,
|
|
141
143
|
starred: true,
|
|
142
144
|
}),
|
|
143
|
-
|
|
145
|
+
threadRow({
|
|
144
146
|
id: "lease",
|
|
145
147
|
accountId: PERSONAL,
|
|
146
148
|
fromName: "Amara Okafor",
|
|
@@ -152,7 +154,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
152
154
|
hasAttachment: true,
|
|
153
155
|
starred: true,
|
|
154
156
|
}),
|
|
155
|
-
|
|
157
|
+
threadRow({
|
|
156
158
|
id: "dinner",
|
|
157
159
|
accountId: PERSONAL,
|
|
158
160
|
fromName: "Grace Hopper",
|
|
@@ -163,7 +165,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
163
165
|
ago: 5 * HOUR,
|
|
164
166
|
isRead: true,
|
|
165
167
|
}),
|
|
166
|
-
|
|
168
|
+
threadRow({
|
|
167
169
|
id: "invoice",
|
|
168
170
|
accountId: WORK,
|
|
169
171
|
fromName: "Billing",
|
|
@@ -175,7 +177,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
175
177
|
isRead: true,
|
|
176
178
|
starred: true,
|
|
177
179
|
}),
|
|
178
|
-
|
|
180
|
+
threadRow({
|
|
179
181
|
id: "shipping",
|
|
180
182
|
accountId: PERSONAL,
|
|
181
183
|
fromName: "Parcel Service",
|
|
@@ -185,7 +187,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
185
187
|
category: "transactional",
|
|
186
188
|
ago: 9 * HOUR,
|
|
187
189
|
}),
|
|
188
|
-
|
|
190
|
+
threadRow({
|
|
189
191
|
id: "weekly-digest",
|
|
190
192
|
accountId: PERSONAL,
|
|
191
193
|
fromName: "The Weekly",
|
|
@@ -195,7 +197,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
195
197
|
category: "newsletter",
|
|
196
198
|
ago: DAY,
|
|
197
199
|
}),
|
|
198
|
-
|
|
200
|
+
threadRow({
|
|
199
201
|
id: "sale",
|
|
200
202
|
accountId: PERSONAL,
|
|
201
203
|
fromName: "Outdoor Store",
|
|
@@ -205,7 +207,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
205
207
|
category: "marketing",
|
|
206
208
|
ago: DAY + 3 * HOUR,
|
|
207
209
|
}),
|
|
208
|
-
|
|
210
|
+
threadRow({
|
|
209
211
|
id: "mention",
|
|
210
212
|
accountId: WORK,
|
|
211
213
|
fromName: "Chat",
|
|
@@ -215,7 +217,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
215
217
|
category: "social",
|
|
216
218
|
ago: DAY + 5 * HOUR,
|
|
217
219
|
}),
|
|
218
|
-
|
|
220
|
+
threadRow({
|
|
219
221
|
id: "build",
|
|
220
222
|
accountId: WORK,
|
|
221
223
|
fromName: "CI",
|
|
@@ -226,7 +228,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
226
228
|
ago: 2 * DAY,
|
|
227
229
|
isRead: true,
|
|
228
230
|
}),
|
|
229
|
-
|
|
231
|
+
threadRow({
|
|
230
232
|
id: "talk-outline",
|
|
231
233
|
accountId: PERSONAL,
|
|
232
234
|
role: "Archive",
|
|
@@ -242,7 +244,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
242
244
|
];
|
|
243
245
|
|
|
244
246
|
const serverDrafts: RemitImapThreadMessageResponse[] = [
|
|
245
|
-
|
|
247
|
+
threadRow({
|
|
246
248
|
id: "draft-talk",
|
|
247
249
|
accountId: PERSONAL,
|
|
248
250
|
role: "Drafts",
|
|
@@ -376,7 +378,7 @@ export const newsletterBacklog = (
|
|
|
376
378
|
count: number,
|
|
377
379
|
): RemitImapThreadMessageResponse[] =>
|
|
378
380
|
Array.from({ length: count }, (_, index) =>
|
|
379
|
-
|
|
381
|
+
threadRow({
|
|
380
382
|
id: `digest-${index}`,
|
|
381
383
|
accountId: PERSONAL,
|
|
382
384
|
fromName: "The Weekly",
|
|
@@ -388,6 +390,29 @@ export const newsletterBacklog = (
|
|
|
388
390
|
}),
|
|
389
391
|
);
|
|
390
392
|
|
|
393
|
+
export const senderAddress = (
|
|
394
|
+
row: RemitImapThreadMessageResponse,
|
|
395
|
+
): RemitImapAddressResponse => {
|
|
396
|
+
const email = (row.fromEmail ?? "").toLowerCase();
|
|
397
|
+
const [localPart = "", domain = ""] = email.split("@");
|
|
398
|
+
return {
|
|
399
|
+
addressId: `addr-${email}`,
|
|
400
|
+
accountConfigId: row.accountConfigId,
|
|
401
|
+
displayName: row.fromName,
|
|
402
|
+
localPart,
|
|
403
|
+
domain,
|
|
404
|
+
normalizedEmail: email,
|
|
405
|
+
flags: {},
|
|
406
|
+
inboundCount: 1,
|
|
407
|
+
outboundCount: 0,
|
|
408
|
+
replyCount: 0,
|
|
409
|
+
lastInboundAt: row.sentDate,
|
|
410
|
+
lastReplyAt: 0,
|
|
411
|
+
createdAt: row.sentDate,
|
|
412
|
+
updatedAt: row.sentDate,
|
|
413
|
+
};
|
|
414
|
+
};
|
|
415
|
+
|
|
391
416
|
export const mailWorld = (overrides: Partial<MailWorld> = {}): MailWorld => ({
|
|
392
417
|
accounts,
|
|
393
418
|
mailboxes: (overrides.accounts ?? accounts).flatMap((account) =>
|
|
@@ -403,5 +428,6 @@ export const mailWorld = (overrides: Partial<MailWorld> = {}): MailWorld => ({
|
|
|
403
428
|
check: { status: "disabled" },
|
|
404
429
|
run: null,
|
|
405
430
|
},
|
|
431
|
+
bodies: {},
|
|
406
432
|
...overrides,
|
|
407
433
|
});
|
|
@@ -144,7 +144,9 @@ const pageShow = (persisted: boolean): Event => {
|
|
|
144
144
|
};
|
|
145
145
|
|
|
146
146
|
/** The real route, mounted the way the generated tree mounts it. */
|
|
147
|
-
const mountAccounts = async (
|
|
147
|
+
const mountAccounts = async (
|
|
148
|
+
entry = "/settings/accounts",
|
|
149
|
+
): Promise<DomHarness> => {
|
|
148
150
|
http = mockFetch((call) => {
|
|
149
151
|
if (call.path.endsWith("/oauth/microsoft/start")) {
|
|
150
152
|
return { authorizationUrl: CONSENT_URL };
|
|
@@ -168,7 +170,7 @@ const mountAccounts = async (): Promise<DomHarness> => {
|
|
|
168
170
|
]) as unknown as AnyRoute;
|
|
169
171
|
const router = createRouter({
|
|
170
172
|
routeTree,
|
|
171
|
-
history: createMemoryHistory({ initialEntries: [
|
|
173
|
+
history: createMemoryHistory({ initialEntries: [entry] }),
|
|
172
174
|
}) as unknown as AnyRouter;
|
|
173
175
|
await router.load();
|
|
174
176
|
|
|
@@ -275,3 +277,69 @@ describe("the Reconnect button and the redirect it starts", () => {
|
|
|
275
277
|
assert.ok(dom.byText("button", "Reconnect"));
|
|
276
278
|
});
|
|
277
279
|
});
|
|
280
|
+
|
|
281
|
+
describe("the Microsoft callback landing on the accounts page (#1183)", () => {
|
|
282
|
+
afterEach(() => {
|
|
283
|
+
harness?.close();
|
|
284
|
+
harness = undefined;
|
|
285
|
+
http?.restore();
|
|
286
|
+
http = undefined;
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("a reconnect that comes back connected ends on the granted screen", async () => {
|
|
290
|
+
const dom = await mountAccounts("/settings/accounts?connected=acc-1");
|
|
291
|
+
await dom.waitFor(
|
|
292
|
+
() => dom.text().includes("Connected matthijs@ischen.nl"),
|
|
293
|
+
"the granted screen",
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
assert.match(dom.text(), /Access granted/);
|
|
297
|
+
dom.click(dom.byText("button", "Go to inbox"));
|
|
298
|
+
await settle(dom);
|
|
299
|
+
assert.match(dom.text(), /Account connected successfully\./);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test("a reconnect Microsoft refused a service reopens that account, address filled", async () => {
|
|
303
|
+
const dom = await mountAccounts(
|
|
304
|
+
"/settings/accounts?oauthError=scope_not_granted&oauthEmail=matthijs%40ischen.nl&missingServices=Calendar",
|
|
305
|
+
);
|
|
306
|
+
await dom.waitFor(
|
|
307
|
+
() => dom.text().includes("Reconnect matthijs@ischen.nl"),
|
|
308
|
+
"the reconnect sign-in",
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
assert.match(dom.text(), /Microsoft did not grant access to Calendar\./);
|
|
312
|
+
assert.equal(
|
|
313
|
+
dom.query<HTMLInputElement>("#microsoft-email")?.value,
|
|
314
|
+
"matthijs@ischen.nl",
|
|
315
|
+
);
|
|
316
|
+
const choice = dom
|
|
317
|
+
.queryAll("fieldset")
|
|
318
|
+
.find((fieldset) =>
|
|
319
|
+
fieldset.textContent?.includes("What should Remit sync?"),
|
|
320
|
+
);
|
|
321
|
+
assert.ok(choice, "expected the service choice");
|
|
322
|
+
assert.deepEqual(
|
|
323
|
+
[
|
|
324
|
+
...choice.querySelectorAll<HTMLInputElement>("input[type=checkbox]"),
|
|
325
|
+
].map((box) => box.checked),
|
|
326
|
+
[true, true],
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test("a new sign-in Microsoft refused a service reopens the add wizard", async () => {
|
|
331
|
+
const dom = await mountAccounts(
|
|
332
|
+
"/settings/accounts?oauthError=scope_not_granted&oauthEmail=new%40outlook.com&missingServices=Mail%2CCalendar",
|
|
333
|
+
);
|
|
334
|
+
await dom.waitFor(
|
|
335
|
+
() => dom.text().includes("did not grant access to Mail and Calendar"),
|
|
336
|
+
"the refused sign-in",
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
assert.doesNotMatch(dom.text(), /Reconnect new@outlook\.com/);
|
|
340
|
+
assert.equal(
|
|
341
|
+
dom.query<HTMLInputElement>("#microsoft-email")?.value,
|
|
342
|
+
"new@outlook.com",
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
7
7
|
import type { RemitImapAccountResponse } from "@remit/api-http-client/types.gen.ts";
|
|
8
8
|
import {
|
|
9
|
+
ACCOUNT_SERVICE_ROWS,
|
|
9
10
|
AccountHealthCard,
|
|
10
11
|
Badge,
|
|
11
12
|
Banner,
|
|
@@ -20,7 +21,10 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
|
|
20
21
|
import { AlertTriangle, Loader2, Plus, Trash2, X } from "lucide-react";
|
|
21
22
|
import { useEffect, useState } from "react";
|
|
22
23
|
import { z } from "zod";
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
type MicrosoftReturn,
|
|
26
|
+
OnboardingWizard,
|
|
27
|
+
} from "@/components/onboarding/OnboardingWizard";
|
|
24
28
|
import { AccountFormPanel } from "@/components/settings/AccountFormPanel";
|
|
25
29
|
import { AccountServices } from "@/components/settings/AccountServices";
|
|
26
30
|
import { DangerZone } from "@/components/settings/DangerZone";
|
|
@@ -41,6 +45,8 @@ const accountsSearchSchema = z.object({
|
|
|
41
45
|
connected: z.string().optional(),
|
|
42
46
|
/** Set by the backend redirect when the OAuth flow fails */
|
|
43
47
|
oauthError: z.string().optional(),
|
|
48
|
+
oauthEmail: z.string().optional(),
|
|
49
|
+
missingServices: z.string().optional(),
|
|
44
50
|
});
|
|
45
51
|
|
|
46
52
|
export const Route = createFileRoute("/settings/accounts")({
|
|
@@ -86,6 +92,43 @@ export function mapOauthError(code: string): string {
|
|
|
86
92
|
return `Sign-in failed: ${code}. Please try again.`;
|
|
87
93
|
}
|
|
88
94
|
|
|
95
|
+
const joinServices = (labels: string[]): string =>
|
|
96
|
+
labels.length > 1
|
|
97
|
+
? `${labels.slice(0, -1).join(", ")} and ${labels[labels.length - 1]}`
|
|
98
|
+
: (labels[0] ?? "");
|
|
99
|
+
|
|
100
|
+
export function scopeRefusalReturn(
|
|
101
|
+
refusal: { oauthEmail?: string; missingServices?: string },
|
|
102
|
+
accounts: Pick<
|
|
103
|
+
RemitImapAccountResponse,
|
|
104
|
+
"email" | "authType" | "syncedServices"
|
|
105
|
+
>[],
|
|
106
|
+
): MicrosoftReturn {
|
|
107
|
+
const named = (refusal.missingServices ?? "").split(",");
|
|
108
|
+
const missing = ACCOUNT_SERVICE_ROWS.filter((row) => named.includes(row.id));
|
|
109
|
+
const email = refusal.oauthEmail;
|
|
110
|
+
const existing = email
|
|
111
|
+
? accounts.find(
|
|
112
|
+
(account) =>
|
|
113
|
+
account.authType === "oauthMicrosoft" &&
|
|
114
|
+
account.email.toLowerCase() === email.toLowerCase(),
|
|
115
|
+
)
|
|
116
|
+
: undefined;
|
|
117
|
+
const labels = joinServices(missing.map((row) => row.label));
|
|
118
|
+
const message =
|
|
119
|
+
missing.length === 0
|
|
120
|
+
? mapOauthError("scope_not_granted")
|
|
121
|
+
: `Microsoft did not grant access to ${labels}. Sign in again and accept the ${labels} ${missing.length > 1 ? "permissions" : "permission"} when Microsoft asks.`;
|
|
122
|
+
const services = existing
|
|
123
|
+
? ACCOUNT_SERVICE_ROWS.filter(
|
|
124
|
+
(row) =>
|
|
125
|
+
existing.syncedServices.includes(row.id) ||
|
|
126
|
+
missing.some((service) => service.id === row.id),
|
|
127
|
+
).map((row) => row.id)
|
|
128
|
+
: undefined;
|
|
129
|
+
return { kind: "refused", message, email, services, reconnect: !!existing };
|
|
130
|
+
}
|
|
131
|
+
|
|
89
132
|
/**
|
|
90
133
|
* Derive a friendly primary label from the email's local part so
|
|
91
134
|
* AccountHealthCard doesn't print the same address twice (label primary, email
|
|
@@ -182,6 +225,9 @@ function AccountsSettings() {
|
|
|
182
225
|
|
|
183
226
|
const [showForm, setShowForm] = useState(false);
|
|
184
227
|
const [showAddWizard, setShowAddWizard] = useState(false);
|
|
228
|
+
const [microsoftReturn, setMicrosoftReturn] = useState<
|
|
229
|
+
MicrosoftReturn | undefined
|
|
230
|
+
>(undefined);
|
|
185
231
|
const [editingAccountId, setEditingAccountId] = useState<string | null>(null);
|
|
186
232
|
const [focusSmtp, setFocusSmtp] = useState(false);
|
|
187
233
|
const [deletingAccountId, setDeletingAccountId] = useState<string | null>(
|
|
@@ -219,6 +265,8 @@ function AccountsSettings() {
|
|
|
219
265
|
focusSmtp: undefined,
|
|
220
266
|
connected: search.connected,
|
|
221
267
|
oauthError: search.oauthError,
|
|
268
|
+
oauthEmail: search.oauthEmail,
|
|
269
|
+
missingServices: search.missingServices,
|
|
222
270
|
},
|
|
223
271
|
replace: true,
|
|
224
272
|
});
|
|
@@ -228,13 +276,15 @@ function AccountsSettings() {
|
|
|
228
276
|
navigate,
|
|
229
277
|
search.connected,
|
|
230
278
|
search.oauthError,
|
|
279
|
+
search.oauthEmail,
|
|
280
|
+
search.missingServices,
|
|
231
281
|
]);
|
|
232
282
|
|
|
233
|
-
// Handle ?connected — show
|
|
283
|
+
// Handle ?connected — show what Microsoft granted, clear param
|
|
234
284
|
useEffect(() => {
|
|
235
285
|
if (!search.connected) return;
|
|
236
|
-
|
|
237
|
-
|
|
286
|
+
setMicrosoftReturn({ kind: "connected", accountId: search.connected });
|
|
287
|
+
setShowAddWizard(true);
|
|
238
288
|
queryClient.invalidateQueries({
|
|
239
289
|
queryKey: configOperationsGetConfigQueryKey(),
|
|
240
290
|
});
|
|
@@ -252,8 +302,23 @@ function AccountsSettings() {
|
|
|
252
302
|
// Handle ?oauthError — show human-readable error, clear param
|
|
253
303
|
useEffect(() => {
|
|
254
304
|
if (!search.oauthError) return;
|
|
255
|
-
|
|
305
|
+
const refusedScope = search.oauthError === "scope_not_granted";
|
|
306
|
+
if (refusedScope && !config && !isError) return;
|
|
256
307
|
setReconnectingAccountId(null);
|
|
308
|
+
if (refusedScope) {
|
|
309
|
+
setMicrosoftReturn(
|
|
310
|
+
scopeRefusalReturn(
|
|
311
|
+
{
|
|
312
|
+
oauthEmail: search.oauthEmail,
|
|
313
|
+
missingServices: search.missingServices,
|
|
314
|
+
},
|
|
315
|
+
config?.accounts ?? [],
|
|
316
|
+
),
|
|
317
|
+
);
|
|
318
|
+
setShowAddWizard(true);
|
|
319
|
+
} else {
|
|
320
|
+
setOauthErrorMessage(mapOauthError(search.oauthError));
|
|
321
|
+
}
|
|
257
322
|
navigate({
|
|
258
323
|
search: {
|
|
259
324
|
oauthError: undefined,
|
|
@@ -263,7 +328,14 @@ function AccountsSettings() {
|
|
|
263
328
|
},
|
|
264
329
|
replace: true,
|
|
265
330
|
});
|
|
266
|
-
}, [
|
|
331
|
+
}, [
|
|
332
|
+
search.oauthError,
|
|
333
|
+
search.oauthEmail,
|
|
334
|
+
search.missingServices,
|
|
335
|
+
config,
|
|
336
|
+
isError,
|
|
337
|
+
navigate,
|
|
338
|
+
]);
|
|
267
339
|
|
|
268
340
|
// Microsoft may hand the finished sign-in back to a different window than the
|
|
269
341
|
// one that started it, so a reconnect can complete without this page ever
|
|
@@ -522,13 +594,21 @@ function AccountsSettings() {
|
|
|
522
594
|
<div className="fixed inset-0 z-40 overflow-auto bg-canvas">
|
|
523
595
|
<OnboardingWizard
|
|
524
596
|
skipWelcome
|
|
597
|
+
microsoftReturn={microsoftReturn}
|
|
525
598
|
onComplete={() => {
|
|
599
|
+
if (microsoftReturn?.kind === "connected") {
|
|
600
|
+
setSuccessMessage("Account connected successfully.");
|
|
601
|
+
}
|
|
526
602
|
setShowAddWizard(false);
|
|
603
|
+
setMicrosoftReturn(undefined);
|
|
527
604
|
queryClient.invalidateQueries({
|
|
528
605
|
queryKey: configOperationsGetConfigQueryKey(),
|
|
529
606
|
});
|
|
530
607
|
}}
|
|
531
|
-
onCancel={() =>
|
|
608
|
+
onCancel={() => {
|
|
609
|
+
setShowAddWizard(false);
|
|
610
|
+
setMicrosoftReturn(undefined);
|
|
611
|
+
}}
|
|
532
612
|
/>
|
|
533
613
|
</div>
|
|
534
614
|
)}
|