@remit/web-client 0.0.315 → 0.0.316
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 +1 -0
- package/src/components/calendar/change-suggestion-first.render.test.ts +1 -0
- package/src/components/calendar/pending-suggestions.render.test.ts +1 -0
- package/src/components/mail/offer-times-reply.render.test.ts +1 -0
- package/src/hooks/calendar/useCalendarSuggestions.ts +24 -1
- package/src/hooks/intelligence-calendar.render.test.ts +52 -7
- package/src/hooks/useIntelligenceCalendar.ts +10 -1
- package/src/lib/calendar-suggestion.test.ts +10 -2
- package/src/lib/calendar-suggestion.ts +6 -4
- package/src/lib/calendar-suggestion.west.test.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.316",
|
|
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": {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
calendarSuggestionActionOperationsAcceptCalendarSuggestionMutation,
|
|
16
16
|
calendarSuggestionActionOperationsDeclineCalendarSuggestionMutation,
|
|
17
17
|
calendarSuggestionActionOperationsDismissCalendarSuggestionMutation,
|
|
18
|
+
calendarSuggestionActionOperationsReopenCalendarSuggestionMutation,
|
|
18
19
|
calendarSuggestionOperationsListCalendarSuggestionsOptions,
|
|
19
20
|
messageCalendarSuggestionOperationsListMessageCalendarSuggestionsOptions,
|
|
20
21
|
} from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
@@ -162,6 +163,7 @@ export interface CalendarSuggestionAnswers {
|
|
|
162
163
|
suggestionId: string,
|
|
163
164
|
muteSender: boolean,
|
|
164
165
|
) => Promise<SuggestionAnswer>;
|
|
166
|
+
reopen: (suggestionId: string) => Promise<SuggestionAnswer>;
|
|
165
167
|
isAnswering: boolean;
|
|
166
168
|
}
|
|
167
169
|
|
|
@@ -188,6 +190,12 @@ export function useCalendarSuggestionAnswers(): CalendarSuggestionAnswers {
|
|
|
188
190
|
onSuccess,
|
|
189
191
|
});
|
|
190
192
|
|
|
193
|
+
const reopen = useMutation({
|
|
194
|
+
...calendarSuggestionActionOperationsReopenCalendarSuggestionMutation(),
|
|
195
|
+
meta: SUGGESTION_META,
|
|
196
|
+
onSuccess,
|
|
197
|
+
});
|
|
198
|
+
|
|
191
199
|
const acceptSuggestion = useCallback(
|
|
192
200
|
(suggestionId: string, calendarId: string) =>
|
|
193
201
|
accept
|
|
@@ -231,6 +239,14 @@ export function useCalendarSuggestionAnswers(): CalendarSuggestionAnswers {
|
|
|
231
239
|
.catch(refused),
|
|
232
240
|
[dismiss],
|
|
233
241
|
);
|
|
242
|
+
const reopenSuggestion = useCallback(
|
|
243
|
+
(suggestionId: string) =>
|
|
244
|
+
reopen
|
|
245
|
+
.mutateAsync({ path: { suggestionId } })
|
|
246
|
+
.then(() => ANSWERED)
|
|
247
|
+
.catch(refused),
|
|
248
|
+
[reopen],
|
|
249
|
+
);
|
|
234
250
|
|
|
235
251
|
return useMemo(
|
|
236
252
|
() => ({
|
|
@@ -239,7 +255,12 @@ export function useCalendarSuggestionAnswers(): CalendarSuggestionAnswers {
|
|
|
239
255
|
acceptEdited: acceptEditedSuggestion,
|
|
240
256
|
decline: declineSuggestion,
|
|
241
257
|
dismiss: dismissSuggestion,
|
|
242
|
-
|
|
258
|
+
reopen: reopenSuggestion,
|
|
259
|
+
isAnswering:
|
|
260
|
+
accept.isPending ||
|
|
261
|
+
decline.isPending ||
|
|
262
|
+
dismiss.isPending ||
|
|
263
|
+
reopen.isPending,
|
|
243
264
|
}),
|
|
244
265
|
[
|
|
245
266
|
acceptSuggestion,
|
|
@@ -247,9 +268,11 @@ export function useCalendarSuggestionAnswers(): CalendarSuggestionAnswers {
|
|
|
247
268
|
acceptEditedSuggestion,
|
|
248
269
|
declineSuggestion,
|
|
249
270
|
dismissSuggestion,
|
|
271
|
+
reopenSuggestion,
|
|
250
272
|
accept.isPending,
|
|
251
273
|
decline.isPending,
|
|
252
274
|
dismiss.isPending,
|
|
275
|
+
reopen.isPending,
|
|
253
276
|
],
|
|
254
277
|
);
|
|
255
278
|
}
|
|
@@ -101,6 +101,7 @@ const invitation = (
|
|
|
101
101
|
supersededByThreadId: "",
|
|
102
102
|
createdAt: 0,
|
|
103
103
|
updatedAt: 0,
|
|
104
|
+
answerOvertakenBy: "None",
|
|
104
105
|
});
|
|
105
106
|
|
|
106
107
|
const Harness = () => {
|
|
@@ -264,19 +265,63 @@ describe("the invitation beside the open message", () => {
|
|
|
264
265
|
press("Stop offering invitations from Alice");
|
|
265
266
|
|
|
266
267
|
await harness?.waitFor(
|
|
267
|
-
() => harness?.text().includes("
|
|
268
|
-
"the card to
|
|
269
|
-
);
|
|
270
|
-
assert.match(
|
|
271
|
-
harness?.text() ?? "",
|
|
272
|
-
/Nothing in this message is about a time/,
|
|
273
|
-
"answering the last card keeps the Calendar tab",
|
|
268
|
+
() => harness?.text().includes("You dismissed this") === true,
|
|
269
|
+
"the card to read back the dismissed state",
|
|
274
270
|
);
|
|
271
|
+
assert.doesNotMatch(harness?.text() ?? "", /Add to calendar/);
|
|
275
272
|
const dismissed =
|
|
276
273
|
http?.to(`/calendar-suggestions/${SUGGESTION}/dismiss`) ?? [];
|
|
277
274
|
assert.equal(dismissed.length, 1);
|
|
278
275
|
assert.deepEqual(dismissed[0].body, { muteSender: true });
|
|
279
276
|
});
|
|
277
|
+
|
|
278
|
+
for (const [label, state] of [
|
|
279
|
+
["Add to calendar", "Accepted"],
|
|
280
|
+
["Maybe", "Tentative"],
|
|
281
|
+
["Decline", "Declined"],
|
|
282
|
+
["Stop offering invitations from Alice", "Dismissed"],
|
|
283
|
+
] as const) {
|
|
284
|
+
it(`takes a ${state} answer back through the API, and asks again`, async () => {
|
|
285
|
+
await mount(
|
|
286
|
+
server((call) => (call.path.endsWith("/reopen") ? "Pending" : state)),
|
|
287
|
+
);
|
|
288
|
+
press(label);
|
|
289
|
+
await harness?.waitFor(
|
|
290
|
+
() => harness?.text().includes("Change") === true,
|
|
291
|
+
"the card to offer Change once answered",
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
press("Change");
|
|
295
|
+
|
|
296
|
+
await harness?.waitFor(
|
|
297
|
+
() => harness?.text().includes("Add to calendar") === true,
|
|
298
|
+
"the answers to be offered again once the server reopened it",
|
|
299
|
+
);
|
|
300
|
+
assert.equal(
|
|
301
|
+
http?.to(`/calendar-suggestions/${SUGGESTION}/reopen`).length,
|
|
302
|
+
1,
|
|
303
|
+
);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
it("offers no Change on an answer its cancellation was answered over", async () => {
|
|
308
|
+
await mount((call) => {
|
|
309
|
+
if (call.path.endsWith("/calendars")) return { items: calendars };
|
|
310
|
+
if (call.path.endsWith("/messages/msg-1/calendar-suggestions"))
|
|
311
|
+
return {
|
|
312
|
+
items: [
|
|
313
|
+
{ ...invitation("Accepted"), answerOvertakenBy: "Cancellation" },
|
|
314
|
+
],
|
|
315
|
+
};
|
|
316
|
+
return { items: [] };
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
await harness?.waitFor(
|
|
320
|
+
() => harness?.text().includes("On your calendar") === true,
|
|
321
|
+
"the card to read back the accepted state",
|
|
322
|
+
);
|
|
323
|
+
assert.equal(harness?.text().includes("Change"), false);
|
|
324
|
+
});
|
|
280
325
|
});
|
|
281
326
|
|
|
282
327
|
(globalThis as { self?: typeof globalThis }).self ??= globalThis;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* outlives the message it is about.
|
|
10
10
|
*/
|
|
11
11
|
import type { RemitImapThreadMessageResponse } from "@remit/api-http-client/types.gen.ts";
|
|
12
|
+
import { CalendarAnswerOvertaken } from "@remit/domain-enums";
|
|
12
13
|
import type {
|
|
13
14
|
CalendarDayEntry,
|
|
14
15
|
CalendarSlotPick,
|
|
@@ -261,6 +262,9 @@ export function useIntelligenceCalendar(
|
|
|
261
262
|
};
|
|
262
263
|
const pickedText =
|
|
263
264
|
date === "" ? "" : slotsAsText(date, slots, working.picked);
|
|
265
|
+
const overtaken =
|
|
266
|
+
invitation !== undefined &&
|
|
267
|
+
invitation.answerOvertakenBy !== CalendarAnswerOvertaken.None;
|
|
264
268
|
|
|
265
269
|
const actions: IntelligenceCalendarActions = {
|
|
266
270
|
onAddInvite: () => {
|
|
@@ -295,7 +299,12 @@ export function useIntelligenceCalendar(
|
|
|
295
299
|
answer(invitationId, "keep this on your calendar", () =>
|
|
296
300
|
answers.dismiss(invitationId, false),
|
|
297
301
|
)
|
|
298
|
-
:
|
|
302
|
+
: overtaken
|
|
303
|
+
? undefined
|
|
304
|
+
: () =>
|
|
305
|
+
answer(invitationId, "take this answer back", () =>
|
|
306
|
+
answers.reopen(invitationId),
|
|
307
|
+
),
|
|
299
308
|
onOpenNewerInvite:
|
|
300
309
|
openThread && invitation && invitation.supersededByThreadId !== ""
|
|
301
310
|
? () =>
|
|
@@ -40,6 +40,7 @@ const suggestion = (
|
|
|
40
40
|
supersededByThreadId: "",
|
|
41
41
|
createdAt: 0,
|
|
42
42
|
updatedAt: 0,
|
|
43
|
+
answerOvertakenBy: "None",
|
|
43
44
|
...over,
|
|
44
45
|
});
|
|
45
46
|
|
|
@@ -70,8 +71,15 @@ describe("inviteStanding", () => {
|
|
|
70
71
|
});
|
|
71
72
|
});
|
|
72
73
|
|
|
73
|
-
it("
|
|
74
|
-
assert.
|
|
74
|
+
it("keeps a card the reader waved away, so it can be taken back", () => {
|
|
75
|
+
assert.deepEqual(inviteStanding(suggestion({ state: "Dismissed" })), {
|
|
76
|
+
state: "dismissed",
|
|
77
|
+
rsvp: "noReply",
|
|
78
|
+
});
|
|
79
|
+
assert.equal(
|
|
80
|
+
inviteStanding(suggestion({ method: "Cancel", state: "Dismissed" })),
|
|
81
|
+
undefined,
|
|
82
|
+
);
|
|
75
83
|
});
|
|
76
84
|
|
|
77
85
|
it("asks about a cancellation only until it is taken or kept", () => {
|
|
@@ -61,18 +61,20 @@ export interface InviteStanding {
|
|
|
61
61
|
/**
|
|
62
62
|
* What a suggestion's state means for its card.
|
|
63
63
|
*
|
|
64
|
-
* A dismissed card was waved away
|
|
65
|
-
* answer only while it is pending: once
|
|
66
|
-
* settled and there is nothing left to
|
|
64
|
+
* A dismissed card was waved away without an answer, and stays so it can be
|
|
65
|
+
* taken back. A cancellation asks for an answer only while it is pending: once
|
|
66
|
+
* taken or kept, the event it named is settled and there is nothing left to
|
|
67
|
+
* say beside the message.
|
|
67
68
|
*/
|
|
68
69
|
export function inviteStanding(
|
|
69
70
|
suggestion: CalendarSuggestion,
|
|
70
71
|
): InviteStanding | undefined {
|
|
71
|
-
if (suggestion.state === "Dismissed") return undefined;
|
|
72
72
|
if (suggestion.method === "Cancel")
|
|
73
73
|
return suggestion.state === "Pending"
|
|
74
74
|
? { state: "cancelled", rsvp: "noReply" }
|
|
75
75
|
: undefined;
|
|
76
|
+
if (suggestion.state === "Dismissed")
|
|
77
|
+
return { state: "dismissed", rsvp: "noReply" };
|
|
76
78
|
if (suggestion.state === "Superseded")
|
|
77
79
|
return { state: "superseded", rsvp: "noReply" };
|
|
78
80
|
if (suggestion.state === "Accepted")
|