@remit/web-client 0.0.314 → 0.0.315
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/calendar/ChangeCalendarSuggestion.stories.tsx +2 -0
- package/src/components/calendar/change-suggestion-first.render.test.ts +2 -0
- package/src/components/calendar/pending-suggestions.render.test.ts +2 -0
- package/src/components/mail/BriefPane.tsx +6 -1
- package/src/components/mail/FlaggedPane.tsx +6 -1
- package/src/components/mail/IntelligenceDrawer.tsx +4 -0
- package/src/components/mail/IntelligencePane.tsx +11 -2
- package/src/components/mail/MailboxPane.tsx +5 -0
- package/src/components/mail/offer-times-reply.render.test.ts +2 -0
- package/src/hooks/intelligence-calendar.render.test.ts +57 -3
- package/src/hooks/useIntelligenceCalendar.ts +10 -0
- package/src/lib/calendar-suggestion.test.ts +2 -0
- package/src/lib/calendar-suggestion.west.test.ts +2 -0
- package/src/test-support/thread-router.ts +54 -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.315",
|
|
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": {
|
|
@@ -325,6 +325,7 @@ function BriefReading() {
|
|
|
325
325
|
actions,
|
|
326
326
|
onReply,
|
|
327
327
|
handleDeselectIfRemoved,
|
|
328
|
+
onOpenThread,
|
|
328
329
|
} = useBriefPane();
|
|
329
330
|
const hasThread = Boolean(conversation);
|
|
330
331
|
const intelligence = useIntelligenceSurface(conversation?.threadId);
|
|
@@ -377,6 +378,7 @@ function BriefReading() {
|
|
|
377
378
|
accountId={selectedThread?.accountId}
|
|
378
379
|
onAfterOptimisticRemove={handleDeselectIfRemoved}
|
|
379
380
|
onReplyWithText={replyWithText}
|
|
381
|
+
onOpenThread={onOpenThread}
|
|
380
382
|
/>
|
|
381
383
|
</>
|
|
382
384
|
);
|
|
@@ -388,7 +390,8 @@ function BriefReading() {
|
|
|
388
390
|
*/
|
|
389
391
|
function BriefIntelligence() {
|
|
390
392
|
const replyWithText = useReplyWithText();
|
|
391
|
-
const { selectedThread, handleDeselectIfRemoved } =
|
|
393
|
+
const { selectedThread, handleDeselectIfRemoved, onOpenThread } =
|
|
394
|
+
useBriefPane();
|
|
392
395
|
const { onToggleIntelligence } = useMailContext();
|
|
393
396
|
|
|
394
397
|
return (
|
|
@@ -399,6 +402,7 @@ function BriefIntelligence() {
|
|
|
399
402
|
accountId={selectedThread?.accountId}
|
|
400
403
|
onAfterOptimisticRemove={handleDeselectIfRemoved}
|
|
401
404
|
onReplyWithText={replyWithText}
|
|
405
|
+
onOpenThread={onOpenThread}
|
|
402
406
|
/>
|
|
403
407
|
);
|
|
404
408
|
}
|
|
@@ -444,6 +448,7 @@ function BriefPhone() {
|
|
|
444
448
|
accountId={selectedThread?.accountId}
|
|
445
449
|
onAfterOptimisticRemove={handleDeselectIfRemoved}
|
|
446
450
|
onReplyWithText={replyWithText}
|
|
451
|
+
onOpenThread={onOpenThread}
|
|
447
452
|
/>
|
|
448
453
|
</>
|
|
449
454
|
);
|
|
@@ -328,6 +328,7 @@ function FlaggedReading() {
|
|
|
328
328
|
actions,
|
|
329
329
|
onReply,
|
|
330
330
|
handleDeselectIfRemoved,
|
|
331
|
+
onOpenThread,
|
|
331
332
|
} = useFlaggedPane();
|
|
332
333
|
const hasThread = Boolean(conversation);
|
|
333
334
|
const intelligence = useIntelligenceSurface(conversation?.threadId);
|
|
@@ -380,6 +381,7 @@ function FlaggedReading() {
|
|
|
380
381
|
accountId={selectedThread?.accountId}
|
|
381
382
|
onAfterOptimisticRemove={handleDeselectIfRemoved}
|
|
382
383
|
onReplyWithText={replyWithText}
|
|
384
|
+
onOpenThread={onOpenThread}
|
|
383
385
|
/>
|
|
384
386
|
</>
|
|
385
387
|
);
|
|
@@ -391,7 +393,8 @@ function FlaggedReading() {
|
|
|
391
393
|
*/
|
|
392
394
|
function FlaggedIntelligence() {
|
|
393
395
|
const replyWithText = useReplyWithText();
|
|
394
|
-
const { selectedThread, handleDeselectIfRemoved } =
|
|
396
|
+
const { selectedThread, handleDeselectIfRemoved, onOpenThread } =
|
|
397
|
+
useFlaggedPane();
|
|
395
398
|
const { onToggleIntelligence } = useMailContext();
|
|
396
399
|
|
|
397
400
|
return (
|
|
@@ -402,6 +405,7 @@ function FlaggedIntelligence() {
|
|
|
402
405
|
accountId={selectedThread?.accountId}
|
|
403
406
|
onAfterOptimisticRemove={handleDeselectIfRemoved}
|
|
404
407
|
onReplyWithText={replyWithText}
|
|
408
|
+
onOpenThread={onOpenThread}
|
|
405
409
|
/>
|
|
406
410
|
);
|
|
407
411
|
}
|
|
@@ -445,6 +449,7 @@ function FlaggedPhone() {
|
|
|
445
449
|
accountId={selectedThread?.accountId}
|
|
446
450
|
onAfterOptimisticRemove={handleDeselectIfRemoved}
|
|
447
451
|
onReplyWithText={replyWithText}
|
|
452
|
+
onOpenThread={onOpenThread}
|
|
448
453
|
/>
|
|
449
454
|
</>
|
|
450
455
|
);
|
|
@@ -2,6 +2,7 @@ import type { RemitImapThreadMessageResponse } from "@remit/api-http-client/type
|
|
|
2
2
|
import type { ReplyWithText } from "@/components/compose/reply-with-times";
|
|
3
3
|
import { Drawer } from "@/components/layout/Drawer";
|
|
4
4
|
import { IntelligencePane } from "@/components/mail/IntelligencePane";
|
|
5
|
+
import type { OpenThreadTarget } from "@/routing";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* The intelligence pane as a modal drawer — the surface every list pane uses
|
|
@@ -18,6 +19,7 @@ export function IntelligenceDrawer({
|
|
|
18
19
|
accountId,
|
|
19
20
|
onAfterOptimisticRemove,
|
|
20
21
|
onReplyWithText,
|
|
22
|
+
onOpenThread,
|
|
21
23
|
}: {
|
|
22
24
|
isOpen: boolean;
|
|
23
25
|
onClose: () => void;
|
|
@@ -26,6 +28,7 @@ export function IntelligenceDrawer({
|
|
|
26
28
|
accountId?: string;
|
|
27
29
|
onAfterOptimisticRemove?: (messageIds: string[]) => void;
|
|
28
30
|
onReplyWithText?: ReplyWithText;
|
|
31
|
+
onOpenThread?: (target: OpenThreadTarget) => void;
|
|
29
32
|
}) {
|
|
30
33
|
return (
|
|
31
34
|
<Drawer
|
|
@@ -45,6 +48,7 @@ export function IntelligenceDrawer({
|
|
|
45
48
|
hideCloseButton
|
|
46
49
|
onAfterOptimisticRemove={onAfterOptimisticRemove}
|
|
47
50
|
onReplyWithText={onReplyWithText}
|
|
51
|
+
onOpenThread={onOpenThread}
|
|
48
52
|
/>
|
|
49
53
|
</Drawer>
|
|
50
54
|
);
|
|
@@ -19,7 +19,7 @@ import { useUpdateAddressFlags } from "@/hooks/useUpdateAddressFlags";
|
|
|
19
19
|
import { isRescueCandidate } from "@/lib/rescue-candidates";
|
|
20
20
|
import { recordRescueSentToJunk } from "@/lib/rescue-telemetry";
|
|
21
21
|
import { useTelemetry } from "@/lib/telemetry-context";
|
|
22
|
-
import { NavLink } from "@/routing";
|
|
22
|
+
import { NavLink, type OpenThreadTarget } from "@/routing";
|
|
23
23
|
import type { ReplyWithText } from "../compose/reply-with-times";
|
|
24
24
|
|
|
25
25
|
export interface IntelligencePaneProps {
|
|
@@ -55,6 +55,7 @@ export interface IntelligencePaneProps {
|
|
|
55
55
|
*/
|
|
56
56
|
onAfterOptimisticRemove?: (messageIds: string[]) => void;
|
|
57
57
|
onReplyWithText?: ReplyWithText;
|
|
58
|
+
onOpenThread?: (target: OpenThreadTarget) => void;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
/**
|
|
@@ -254,6 +255,7 @@ interface WiredPanelProps {
|
|
|
254
255
|
hideCloseButton?: boolean;
|
|
255
256
|
onAfterOptimisticRemove?: (messageIds: string[]) => void;
|
|
256
257
|
onReplyWithText?: ReplyWithText;
|
|
258
|
+
onOpenThread?: (target: OpenThreadTarget) => void;
|
|
257
259
|
}
|
|
258
260
|
|
|
259
261
|
/**
|
|
@@ -267,6 +269,7 @@ function WiredPanel({
|
|
|
267
269
|
hideCloseButton,
|
|
268
270
|
onAfterOptimisticRemove,
|
|
269
271
|
onReplyWithText,
|
|
272
|
+
onOpenThread,
|
|
270
273
|
}: WiredPanelProps) {
|
|
271
274
|
const {
|
|
272
275
|
data,
|
|
@@ -277,7 +280,11 @@ function WiredPanel({
|
|
|
277
280
|
similarErrorIsFatal,
|
|
278
281
|
semanticEnabled,
|
|
279
282
|
} = useIntelligenceData(thread, mailboxId);
|
|
280
|
-
const calendar = useIntelligenceCalendar(
|
|
283
|
+
const calendar = useIntelligenceCalendar(
|
|
284
|
+
thread,
|
|
285
|
+
onReplyWithText,
|
|
286
|
+
onOpenThread,
|
|
287
|
+
);
|
|
281
288
|
const [reclassifyOpen, setReclassifyOpen] = useState(false);
|
|
282
289
|
const senderEmail = thread.fromEmail ?? undefined;
|
|
283
290
|
|
|
@@ -437,6 +444,7 @@ export const IntelligencePane = ({
|
|
|
437
444
|
hideCloseButton,
|
|
438
445
|
onAfterOptimisticRemove,
|
|
439
446
|
onReplyWithText,
|
|
447
|
+
onOpenThread,
|
|
440
448
|
}: IntelligencePaneProps) => {
|
|
441
449
|
if (!thread) {
|
|
442
450
|
return (
|
|
@@ -467,6 +475,7 @@ export const IntelligencePane = ({
|
|
|
467
475
|
hideCloseButton={hideCloseButton}
|
|
468
476
|
onAfterOptimisticRemove={onAfterOptimisticRemove}
|
|
469
477
|
onReplyWithText={onReplyWithText}
|
|
478
|
+
onOpenThread={onOpenThread}
|
|
470
479
|
/>
|
|
471
480
|
);
|
|
472
481
|
};
|
|
@@ -1130,6 +1130,7 @@ function MailboxReading() {
|
|
|
1130
1130
|
onToolbarMove,
|
|
1131
1131
|
handleDeselectIfRemoved,
|
|
1132
1132
|
intelligenceRef,
|
|
1133
|
+
onOpenThread,
|
|
1133
1134
|
} = useMailboxPane();
|
|
1134
1135
|
const hasThread = Boolean(conversation);
|
|
1135
1136
|
const intelligence = useIntelligenceSurface(conversation?.threadId);
|
|
@@ -1207,6 +1208,7 @@ function MailboxReading() {
|
|
|
1207
1208
|
accountId={mailboxAccountId}
|
|
1208
1209
|
onAfterOptimisticRemove={handleDeselectIfRemoved}
|
|
1209
1210
|
onReplyWithText={replyWithText}
|
|
1211
|
+
onOpenThread={onOpenThread}
|
|
1210
1212
|
/>
|
|
1211
1213
|
</>
|
|
1212
1214
|
);
|
|
@@ -1224,6 +1226,7 @@ function MailboxIntelligence() {
|
|
|
1224
1226
|
selectedThread,
|
|
1225
1227
|
onToggleIntelligence,
|
|
1226
1228
|
handleDeselectIfRemoved,
|
|
1229
|
+
onOpenThread,
|
|
1227
1230
|
} = useMailboxPane();
|
|
1228
1231
|
|
|
1229
1232
|
return (
|
|
@@ -1234,6 +1237,7 @@ function MailboxIntelligence() {
|
|
|
1234
1237
|
accountId={mailboxAccountId}
|
|
1235
1238
|
onAfterOptimisticRemove={handleDeselectIfRemoved}
|
|
1236
1239
|
onReplyWithText={replyWithText}
|
|
1240
|
+
onOpenThread={onOpenThread}
|
|
1237
1241
|
/>
|
|
1238
1242
|
);
|
|
1239
1243
|
}
|
|
@@ -1288,6 +1292,7 @@ function MailboxPhone() {
|
|
|
1288
1292
|
accountId={mailboxAccountId}
|
|
1289
1293
|
onAfterOptimisticRemove={handleDeselectIfRemoved}
|
|
1290
1294
|
onReplyWithText={replyWithText}
|
|
1295
|
+
onOpenThread={onOpenThread}
|
|
1291
1296
|
/>
|
|
1292
1297
|
</>
|
|
1293
1298
|
);
|
|
@@ -149,6 +149,8 @@ const invitation: RemitImapCalendarSuggestionResponse = {
|
|
|
149
149
|
organizer: "organizer@example.test",
|
|
150
150
|
zoneCertainty: "Explicit",
|
|
151
151
|
acceptedCalendarObjectId: "",
|
|
152
|
+
supersededByMessageId: "",
|
|
153
|
+
supersededByThreadId: "",
|
|
152
154
|
createdAt: 0,
|
|
153
155
|
updatedAt: 0,
|
|
154
156
|
};
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
RouterProvider,
|
|
24
24
|
} from "@tanstack/react-router";
|
|
25
25
|
import { createElement } from "react";
|
|
26
|
+
import { useOpenThread } from "@/routing";
|
|
26
27
|
import { createDomHarness, type DomHarness } from "@/test-support/dom";
|
|
27
28
|
import { makeThreadMessage } from "@/test-support/fixtures";
|
|
28
29
|
import {
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
httpError,
|
|
32
33
|
mockFetch,
|
|
33
34
|
} from "@/test-support/http";
|
|
35
|
+
import { threadRouter } from "@/test-support/thread-router";
|
|
34
36
|
import { useIntelligenceCalendar } from "./useIntelligenceCalendar";
|
|
35
37
|
|
|
36
38
|
const CALENDAR = "11111111-1111-4111-8111-111111111111";
|
|
@@ -95,12 +97,14 @@ const invitation = (
|
|
|
95
97
|
organizer: "organizer@example.test",
|
|
96
98
|
zoneCertainty: "Explicit",
|
|
97
99
|
acceptedCalendarObjectId: "",
|
|
100
|
+
supersededByMessageId: "",
|
|
101
|
+
supersededByThreadId: "",
|
|
98
102
|
createdAt: 0,
|
|
99
103
|
updatedAt: 0,
|
|
100
104
|
});
|
|
101
105
|
|
|
102
106
|
const Harness = () => {
|
|
103
|
-
const calendar = useIntelligenceCalendar(thread);
|
|
107
|
+
const calendar = useIntelligenceCalendar(thread, undefined, useOpenThread());
|
|
104
108
|
return createElement(IntelligencePanel, {
|
|
105
109
|
data: sender,
|
|
106
110
|
calendar: calendar.surface,
|
|
@@ -146,14 +150,19 @@ const server = (
|
|
|
146
150
|
};
|
|
147
151
|
};
|
|
148
152
|
|
|
149
|
-
const mount = async (
|
|
153
|
+
const mount = async (
|
|
154
|
+
respond: (call: HttpCall) => unknown,
|
|
155
|
+
): Promise<AnyRouter> => {
|
|
150
156
|
http = mockFetch(respond);
|
|
151
157
|
harness = createDomHarness();
|
|
152
|
-
|
|
158
|
+
const router = threadRouter(Harness, "/mail/brief/thread-1/msg-1");
|
|
159
|
+
await router.load();
|
|
160
|
+
harness.renderApp(createElement(RouterProvider, { router }));
|
|
153
161
|
await harness.waitFor(
|
|
154
162
|
() => harness?.text().includes("Quarterly review") === true,
|
|
155
163
|
"the invitation to be drawn",
|
|
156
164
|
);
|
|
165
|
+
return router;
|
|
157
166
|
};
|
|
158
167
|
|
|
159
168
|
const press = (label: string) => {
|
|
@@ -396,3 +405,48 @@ describe("changing a suggestion beside the message before adding it", () => {
|
|
|
396
405
|
assert.equal(router.state.location.pathname, "/mail");
|
|
397
406
|
});
|
|
398
407
|
});
|
|
408
|
+
|
|
409
|
+
describe("an invitation a newer revision replaced", () => {
|
|
410
|
+
const retired = (
|
|
411
|
+
supersededByThreadId: string,
|
|
412
|
+
): RemitImapCalendarSuggestionResponse => ({
|
|
413
|
+
...invitation("Superseded"),
|
|
414
|
+
supersededByMessageId: "msg-2",
|
|
415
|
+
supersededByThreadId,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const serving =
|
|
419
|
+
(suggestion: RemitImapCalendarSuggestionResponse) =>
|
|
420
|
+
(call: HttpCall): unknown => {
|
|
421
|
+
if (call.path.endsWith("/calendars")) return { items: calendars };
|
|
422
|
+
if (call.path.endsWith("/messages/msg-1/calendar-suggestions"))
|
|
423
|
+
return { items: [suggestion] };
|
|
424
|
+
return { items: [] };
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
it("opens the conversation that carries the newer revision", async () => {
|
|
428
|
+
const router = await mount(serving(retired("thread-2")));
|
|
429
|
+
assert.match(harness?.text() ?? "", /has sent a newer version of this/);
|
|
430
|
+
|
|
431
|
+
press("Open the newer invitation");
|
|
432
|
+
|
|
433
|
+
await harness?.waitFor(
|
|
434
|
+
() => router.state.location.pathname === "/mail/brief/thread-2/msg-2",
|
|
435
|
+
"the reading pane to move to the newer revision",
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("offers no way there when the newer message sits in no conversation", async () => {
|
|
440
|
+
await mount(serving(retired("")));
|
|
441
|
+
|
|
442
|
+
assert.match(harness?.text() ?? "", /has sent a newer version of this/);
|
|
443
|
+
assert.equal(
|
|
444
|
+
harness
|
|
445
|
+
?.queryAll("button")
|
|
446
|
+
.some((button) =>
|
|
447
|
+
button.textContent?.includes("Open the newer invitation"),
|
|
448
|
+
),
|
|
449
|
+
false,
|
|
450
|
+
);
|
|
451
|
+
});
|
|
452
|
+
});
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
toEventSuggestion,
|
|
52
52
|
} from "@/lib/calendar-suggestion";
|
|
53
53
|
import {
|
|
54
|
+
type OpenThreadTarget,
|
|
54
55
|
useChangeSuggestionOnCalendar,
|
|
55
56
|
useOpenEventOnCalendar,
|
|
56
57
|
} from "@/routing";
|
|
@@ -95,6 +96,7 @@ export interface IntelligenceCalendar {
|
|
|
95
96
|
export function useIntelligenceCalendar(
|
|
96
97
|
thread: RemitImapThreadMessageResponse,
|
|
97
98
|
replyWithText?: ReplyWithText,
|
|
99
|
+
openThread?: (target: OpenThreadTarget) => void,
|
|
98
100
|
): IntelligenceCalendar {
|
|
99
101
|
const messageId = thread.messageId;
|
|
100
102
|
const { suggestions, isLoading, error } =
|
|
@@ -294,6 +296,14 @@ export function useIntelligenceCalendar(
|
|
|
294
296
|
answers.dismiss(invitationId, false),
|
|
295
297
|
)
|
|
296
298
|
: undefined,
|
|
299
|
+
onOpenNewerInvite:
|
|
300
|
+
openThread && invitation && invitation.supersededByThreadId !== ""
|
|
301
|
+
? () =>
|
|
302
|
+
openThread({
|
|
303
|
+
threadId: invitation.supersededByThreadId,
|
|
304
|
+
messageId: invitation.supersededByMessageId,
|
|
305
|
+
})
|
|
306
|
+
: undefined,
|
|
297
307
|
onOfferOtherTimes: () => update({ offering: true }),
|
|
298
308
|
onToggleSlot: (slot) =>
|
|
299
309
|
update({
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AnyRouter,
|
|
3
|
+
createMemoryHistory,
|
|
4
|
+
createRootRoute,
|
|
5
|
+
createRoute,
|
|
6
|
+
createRouter,
|
|
7
|
+
Outlet,
|
|
8
|
+
} from "@tanstack/react-router";
|
|
9
|
+
import { createElement, type FunctionComponent } from "react";
|
|
10
|
+
|
|
11
|
+
(globalThis as { self?: typeof globalThis }).self ??= globalThis;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The brief's thread routes, with `surface` mounted above them so it stays on
|
|
15
|
+
* screen while the address moves. A surface that opens a conversation is
|
|
16
|
+
* asserted by where the router lands, not by the callback it was handed.
|
|
17
|
+
*/
|
|
18
|
+
export const threadRouter = (
|
|
19
|
+
surface: FunctionComponent,
|
|
20
|
+
path: string,
|
|
21
|
+
): AnyRouter => {
|
|
22
|
+
const rootRoute = createRootRoute({
|
|
23
|
+
component: () =>
|
|
24
|
+
createElement("div", null, createElement(surface), createElement(Outlet)),
|
|
25
|
+
});
|
|
26
|
+
const mailRoute = createRoute({
|
|
27
|
+
getParentRoute: () => rootRoute,
|
|
28
|
+
path: "/mail",
|
|
29
|
+
component: Outlet,
|
|
30
|
+
});
|
|
31
|
+
const briefRoute = createRoute({
|
|
32
|
+
getParentRoute: () => mailRoute,
|
|
33
|
+
path: "/brief",
|
|
34
|
+
component: Outlet,
|
|
35
|
+
});
|
|
36
|
+
const threadRoute = createRoute({
|
|
37
|
+
getParentRoute: () => briefRoute,
|
|
38
|
+
path: "$threadId",
|
|
39
|
+
component: Outlet,
|
|
40
|
+
});
|
|
41
|
+
const messageRoute = createRoute({
|
|
42
|
+
getParentRoute: () => threadRoute,
|
|
43
|
+
path: "$messageId",
|
|
44
|
+
component: Outlet,
|
|
45
|
+
});
|
|
46
|
+
return createRouter({
|
|
47
|
+
routeTree: rootRoute.addChildren([
|
|
48
|
+
mailRoute.addChildren([
|
|
49
|
+
briefRoute.addChildren([threadRoute.addChildren([messageRoute])]),
|
|
50
|
+
]),
|
|
51
|
+
]),
|
|
52
|
+
history: createMemoryHistory({ initialEntries: [path] }),
|
|
53
|
+
}) as unknown as AnyRouter;
|
|
54
|
+
};
|