@remit/web-client 0.0.313 → 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/CalendarComposePane.tsx +15 -2
- package/src/components/calendar/CalendarWaiting.tsx +3 -0
- package/src/components/calendar/ChangeCalendarSuggestion.stories.tsx +184 -0
- package/src/components/calendar/ChangeCalendarSuggestion.tsx +159 -0
- package/src/components/calendar/PendingSuggestions.tsx +35 -5
- package/src/components/calendar/calendar-story-server.tsx +5 -0
- package/src/components/calendar/change-suggestion-first.render.test.ts +301 -0
- package/src/components/calendar/pending-suggestions.render.test.ts +70 -3
- 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/calendar/draft.ts +1 -1
- package/src/hooks/calendar/index.ts +7 -0
- package/src/hooks/calendar/suggestion-draft.ts +77 -0
- package/src/hooks/calendar/useCalendarSuggestions.ts +59 -1
- package/src/hooks/intelligence-calendar.render.test.ts +192 -3
- package/src/hooks/useIntelligenceCalendar.ts +46 -5
- package/src/lib/calendar-suggestion.test.ts +12 -1
- package/src/lib/calendar-suggestion.ts +12 -2
- package/src/lib/calendar-suggestion.west.test.ts +2 -0
- package/src/routeTree.gen.ts +23 -0
- package/src/routes/calendar/$view.$date/suggestion.$suggestionId.tsx +22 -0
- package/src/routes/calendar/$view.$date.tsx +9 -3
- package/src/routes/calendar/-calendar-tree.test.ts +18 -0
- package/src/routing/calendar.ts +36 -1
- package/src/routing/index.ts +2 -0
- package/src/test-support/thread-router.ts +54 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// biome-ignore lint/style/useFilenamingConvention: TanStack Router convention
|
|
2
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
3
|
+
import { ChangeCalendarSuggestion } from "@/components/calendar/ChangeCalendarSuggestion";
|
|
4
|
+
import { useCalendarNavigation, useChangingSuggestionId } from "@/routing";
|
|
5
|
+
|
|
6
|
+
function ChangeSuggestionRoute() {
|
|
7
|
+
const suggestionId = useChangingSuggestionId();
|
|
8
|
+
const { closeEvent } = useCalendarNavigation();
|
|
9
|
+
if (suggestionId === undefined) return null;
|
|
10
|
+
return (
|
|
11
|
+
<ChangeCalendarSuggestion
|
|
12
|
+
suggestionId={suggestionId}
|
|
13
|
+
onClose={closeEvent}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const Route = createFileRoute(
|
|
19
|
+
"/calendar/$view/$date/suggestion/$suggestionId",
|
|
20
|
+
)({
|
|
21
|
+
component: ChangeSuggestionRoute,
|
|
22
|
+
});
|
|
@@ -86,8 +86,14 @@ function CalendarView({ isPhone }: { isPhone: boolean }) {
|
|
|
86
86
|
const drawsGrid = !calendarViewMountsAgenda(view);
|
|
87
87
|
const { events, colorByCalendarId, isLoading, error, retry, instanceOf } =
|
|
88
88
|
useCalendarData({ view, date, calendarIds, enabled: drawsGrid });
|
|
89
|
-
const {
|
|
90
|
-
|
|
89
|
+
const {
|
|
90
|
+
goToView,
|
|
91
|
+
goToToday,
|
|
92
|
+
step,
|
|
93
|
+
openEvent,
|
|
94
|
+
openComposer,
|
|
95
|
+
changeSuggestionFirst,
|
|
96
|
+
} = useCalendarNavigation();
|
|
91
97
|
const openedEvent = useOpenCalendarEvent();
|
|
92
98
|
const isWriting = useIsWritingEvent();
|
|
93
99
|
// Held in state rather than read back from storage each render: how much of
|
|
@@ -123,7 +129,7 @@ function CalendarView({ isPhone }: { isPhone: boolean }) {
|
|
|
123
129
|
events={events}
|
|
124
130
|
colorByCalendarId={colorByCalendarId}
|
|
125
131
|
agenda={<AgendaView density={density} onPickSlot={pickSlot} />}
|
|
126
|
-
waiting={<PendingSuggestions />}
|
|
132
|
+
waiting={<PendingSuggestions onChangeFirst={changeSuggestionFirst} />}
|
|
127
133
|
isLoading={isLoading}
|
|
128
134
|
error={error}
|
|
129
135
|
onRetry={retry}
|
|
@@ -31,6 +31,7 @@ import { Route as EventIndexRoute } from "./$view.$date/$calendarObjectId/index.
|
|
|
31
31
|
import { Route as EventRoute } from "./$view.$date/$calendarObjectId.js";
|
|
32
32
|
import { Route as ReadingPaneRoute } from "./$view.$date/index.js";
|
|
33
33
|
import { Route as ComposeRoute } from "./$view.$date/new.js";
|
|
34
|
+
import { Route as SuggestionRoute } from "./$view.$date/suggestion.$suggestionId.js";
|
|
34
35
|
import { Route as ViewRoute } from "./$view.$date.js";
|
|
35
36
|
import { Route as CalendarIndexRoute } from "./index.js";
|
|
36
37
|
|
|
@@ -68,6 +69,10 @@ eventRoute.addChildren([
|
|
|
68
69
|
viewRoute.addChildren([
|
|
69
70
|
attach(ReadingPaneRoute, { path: "/", getParentRoute: () => viewRoute }),
|
|
70
71
|
attach(ComposeRoute, { path: "new", getParentRoute: () => viewRoute }),
|
|
72
|
+
attach(SuggestionRoute, {
|
|
73
|
+
path: "suggestion/$suggestionId",
|
|
74
|
+
getParentRoute: () => viewRoute,
|
|
75
|
+
}),
|
|
71
76
|
eventRoute,
|
|
72
77
|
]);
|
|
73
78
|
const routeTree = rootRoute.addChildren([
|
|
@@ -253,6 +258,19 @@ describe("the composer and the open event cannot both be addressed", () => {
|
|
|
253
258
|
assert.equal(mounted.routeIds.includes("/calendar/$view/$date/new"), false);
|
|
254
259
|
});
|
|
255
260
|
|
|
261
|
+
it("mounts the editor for a suggestion, and nothing of the event route", () => {
|
|
262
|
+
const mounted = match("/calendar/day/2026-06-11/suggestion/sug_1");
|
|
263
|
+
assert.equal(
|
|
264
|
+
mounted.routeIds.at(-1),
|
|
265
|
+
"/calendar/$view/$date/suggestion/$suggestionId",
|
|
266
|
+
);
|
|
267
|
+
assert.equal(mounted.params.suggestionId, "sug_1");
|
|
268
|
+
assert.equal(
|
|
269
|
+
mounted.routeIds.some((id) => id.includes("$calendarObjectId")),
|
|
270
|
+
false,
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
256
274
|
it("keeps the series matched under one of its occurrences", () => {
|
|
257
275
|
const mounted = match(
|
|
258
276
|
"/calendar/week/2026-06-10/evt_1/2026-06-11T09%3A15%3A00%2B02%3A00",
|
package/src/routing/calendar.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
import { useRetainOpenPanels } from "./fragment";
|
|
10
10
|
|
|
11
11
|
const VIEW_ROUTE = "/calendar/$view/$date" as const;
|
|
12
|
+
const SUGGESTION_ROUTE =
|
|
13
|
+
"/calendar/$view/$date/suggestion/$suggestionId" as const;
|
|
12
14
|
|
|
13
15
|
/** Which zoom the calendar is at, on which day, over which calendars. */
|
|
14
16
|
export interface CalendarAddress {
|
|
@@ -67,12 +69,21 @@ export function useOpenCalendarEvent(): OpenCalendarEvent | undefined {
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
/** Whether the composer is up, which is a route rather than a flag. */
|
|
72
|
+
export function useChangingSuggestionId(): string | undefined {
|
|
73
|
+
return useParams({ from: SUGGESTION_ROUTE, shouldThrow: false })
|
|
74
|
+
?.suggestionId;
|
|
75
|
+
}
|
|
76
|
+
|
|
70
77
|
export function useIsWritingEvent(): boolean {
|
|
71
78
|
const composer = useParams({
|
|
72
79
|
from: "/calendar/$view/$date/new",
|
|
73
80
|
shouldThrow: false,
|
|
74
81
|
});
|
|
75
|
-
|
|
82
|
+
const changing = useParams({
|
|
83
|
+
from: SUGGESTION_ROUTE,
|
|
84
|
+
shouldThrow: false,
|
|
85
|
+
});
|
|
86
|
+
return composer !== undefined || changing !== undefined;
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
export interface CalendarNavigation {
|
|
@@ -97,6 +108,7 @@ export interface CalendarNavigation {
|
|
|
97
108
|
openEvent: (calendarObjectId: string, recurrenceId?: string) => void;
|
|
98
109
|
closeEvent: () => void;
|
|
99
110
|
openComposer: () => void;
|
|
111
|
+
changeSuggestionFirst: (suggestionId: string, date: string) => void;
|
|
100
112
|
}
|
|
101
113
|
|
|
102
114
|
/**
|
|
@@ -161,6 +173,13 @@ export function useCalendarNavigation(): CalendarNavigation {
|
|
|
161
173
|
search: true,
|
|
162
174
|
hash: retainPanels,
|
|
163
175
|
}),
|
|
176
|
+
changeSuggestionFirst: (suggestionId: string, on: string) =>
|
|
177
|
+
navigate({
|
|
178
|
+
to: SUGGESTION_ROUTE,
|
|
179
|
+
params: { view, date: on, suggestionId },
|
|
180
|
+
search: true,
|
|
181
|
+
hash: retainPanels,
|
|
182
|
+
}),
|
|
164
183
|
}),
|
|
165
184
|
[goTo, navigate, retainPanels, view, date],
|
|
166
185
|
);
|
|
@@ -194,3 +213,19 @@ export function useOpenEventOnCalendar(): (
|
|
|
194
213
|
[navigate],
|
|
195
214
|
);
|
|
196
215
|
}
|
|
216
|
+
|
|
217
|
+
export function useChangeSuggestionOnCalendar(): (
|
|
218
|
+
date: string,
|
|
219
|
+
suggestionId: string,
|
|
220
|
+
) => void {
|
|
221
|
+
const navigate = useNavigate();
|
|
222
|
+
return useCallback(
|
|
223
|
+
(date: string, suggestionId: string) => {
|
|
224
|
+
navigate({
|
|
225
|
+
to: SUGGESTION_ROUTE,
|
|
226
|
+
params: { view: "day", date, suggestionId },
|
|
227
|
+
});
|
|
228
|
+
},
|
|
229
|
+
[navigate],
|
|
230
|
+
);
|
|
231
|
+
}
|
package/src/routing/index.ts
CHANGED
|
@@ -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
|
+
};
|