@remit/web-client 0.0.289 → 0.0.291
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/auth/AccountMenu.tsx +1 -42
- package/src/components/ui/AppVersion.stories.tsx +31 -0
- package/src/components/ui/BugReportButton.stories.tsx +19 -0
- package/src/components/ui/FatalErrorOverlay.stories.tsx +55 -0
- package/src/hooks/calendar/calendar-writes.render.test.ts +21 -0
- package/src/hooks/calendar/useCalendarWrites.ts +5 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/web-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.291",
|
|
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": {
|
package/src/auth/AccountMenu.tsx
CHANGED
|
@@ -1,47 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { LogOut } from "lucide-react";
|
|
3
|
-
import {
|
|
4
|
-
DropdownMenu,
|
|
5
|
-
DropdownMenuItem,
|
|
6
|
-
DropdownMenuSeparator,
|
|
7
|
-
} from "@/components/ui/DropdownMenu";
|
|
1
|
+
import { AccountMenu as AccountMenuView } from "@remit/ui";
|
|
8
2
|
import { AccountSession } from "./AccountSession";
|
|
9
3
|
|
|
10
|
-
interface AccountMenuViewProps {
|
|
11
|
-
email: string | null;
|
|
12
|
-
onSignOut: () => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const AccountMenuView = ({ email, onSignOut }: AccountMenuViewProps) => {
|
|
16
|
-
const displayName = email ?? "Account";
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<DropdownMenu
|
|
20
|
-
trigger={
|
|
21
|
-
<Avatar name={displayName} email={email ?? undefined} size="sm" />
|
|
22
|
-
}
|
|
23
|
-
align="right"
|
|
24
|
-
>
|
|
25
|
-
{email && (
|
|
26
|
-
<>
|
|
27
|
-
<div
|
|
28
|
-
className="px-3 py-2 text-xs text-fg-muted truncate"
|
|
29
|
-
title={email}
|
|
30
|
-
data-testid="account-menu-email"
|
|
31
|
-
>
|
|
32
|
-
{email}
|
|
33
|
-
</div>
|
|
34
|
-
<DropdownMenuSeparator />
|
|
35
|
-
</>
|
|
36
|
-
)}
|
|
37
|
-
<DropdownMenuItem onClick={onSignOut}>
|
|
38
|
-
<LogOut className="size-4" />
|
|
39
|
-
Sign out
|
|
40
|
-
</DropdownMenuItem>
|
|
41
|
-
</DropdownMenu>
|
|
42
|
-
);
|
|
43
|
-
};
|
|
44
|
-
|
|
45
4
|
export const AccountMenu = () => (
|
|
46
5
|
<AccountSession>
|
|
47
6
|
{({ email, signOut }) => (
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { AppVersion } from "./AppVersion";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof AppVersion> = {
|
|
5
|
+
title: "Playground/Shipped/Settings/Advanced/AppVersion",
|
|
6
|
+
component: AppVersion,
|
|
7
|
+
parameters: { layout: "padded" },
|
|
8
|
+
args: {
|
|
9
|
+
sha: "a1b2c3d",
|
|
10
|
+
commitUrl: "https://github.com/remit-mail/reader/commit/a1b2c3d4e5f6",
|
|
11
|
+
buildTime: "2024-06-12T10:30:00.000Z",
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
export default meta;
|
|
15
|
+
|
|
16
|
+
type Story = StoryObj<typeof AppVersion>;
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {};
|
|
19
|
+
|
|
20
|
+
export const DevBuild: Story = {
|
|
21
|
+
args: { sha: "dev", commitUrl: undefined },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const InAboutSection: Story = {
|
|
25
|
+
render: (args) => (
|
|
26
|
+
<div className="mt-4 max-w-sm border-t border-line pt-4">
|
|
27
|
+
<p className="mb-1 text-sm font-medium text-fg">About</p>
|
|
28
|
+
<AppVersion {...args} />
|
|
29
|
+
</div>
|
|
30
|
+
),
|
|
31
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { BugReportButton } from "./BugReportButton";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof BugReportButton> = {
|
|
5
|
+
title: "Playground/Shipped/Mail/Nav/BugReportButton",
|
|
6
|
+
component: BugReportButton,
|
|
7
|
+
parameters: { layout: "padded" },
|
|
8
|
+
};
|
|
9
|
+
export default meta;
|
|
10
|
+
|
|
11
|
+
type Story = StoryObj<typeof BugReportButton>;
|
|
12
|
+
|
|
13
|
+
export const Default: Story = {
|
|
14
|
+
render: () => (
|
|
15
|
+
<div className="w-64">
|
|
16
|
+
<BugReportButton />
|
|
17
|
+
</div>
|
|
18
|
+
),
|
|
19
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect, within } from "storybook/test";
|
|
3
|
+
import type { FatalError } from "@/lib/fatal-error";
|
|
4
|
+
import { FatalErrorScreen } from "./FatalErrorOverlay";
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof FatalErrorScreen> = {
|
|
7
|
+
title: "Playground/Shipped/App/FatalErrorOverlay",
|
|
8
|
+
component: FatalErrorScreen,
|
|
9
|
+
parameters: { layout: "fullscreen" },
|
|
10
|
+
};
|
|
11
|
+
export default meta;
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof FatalErrorScreen>;
|
|
14
|
+
|
|
15
|
+
const fatal = (overrides: Partial<FatalError>): FatalError => ({
|
|
16
|
+
error: new Error(overrides.message),
|
|
17
|
+
message: "",
|
|
18
|
+
correlationId: "a1b2c3d4-5678-90ab-cdef-1234567890ab",
|
|
19
|
+
at: Date.UTC(2024, 5, 12, 10, 30),
|
|
20
|
+
recoverable: true,
|
|
21
|
+
...overrides,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const RecoverableTransient: Story = {
|
|
25
|
+
args: {
|
|
26
|
+
fatal: fatal({
|
|
27
|
+
message: "Request failed with status 500",
|
|
28
|
+
recoverable: true,
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
play: async ({ canvasElement }) => {
|
|
32
|
+
const canvas = within(canvasElement);
|
|
33
|
+
await expect(canvas.getByRole("button", { name: "Retry" })).toBeVisible();
|
|
34
|
+
await expect(
|
|
35
|
+
canvas.queryByRole("link", { name: "Go to inbox" }),
|
|
36
|
+
).toBeNull();
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const DeterministicFatal: Story = {
|
|
41
|
+
args: {
|
|
42
|
+
fatal: fatal({
|
|
43
|
+
message: "date value is not finite in DateTimeFormat format()",
|
|
44
|
+
correlationId: "def45678-90ab-cdef-1234-567890abcdef",
|
|
45
|
+
recoverable: false,
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
play: async ({ canvasElement }) => {
|
|
49
|
+
const canvas = within(canvasElement);
|
|
50
|
+
await expect(
|
|
51
|
+
canvas.getByRole("link", { name: "Go to inbox" }),
|
|
52
|
+
).toBeVisible();
|
|
53
|
+
await expect(canvas.queryByRole("button", { name: "Retry" })).toBeNull();
|
|
54
|
+
},
|
|
55
|
+
};
|
|
@@ -191,6 +191,27 @@ describe("editing one occurrence of a series", () => {
|
|
|
191
191
|
);
|
|
192
192
|
});
|
|
193
193
|
|
|
194
|
+
it("names the occurrence a whole-series edit was made from", async () => {
|
|
195
|
+
await mount(() => ({ items: [] }));
|
|
196
|
+
await write((writes) =>
|
|
197
|
+
writes.updateEvent(
|
|
198
|
+
{ ...scopedWrite, scope: "all" },
|
|
199
|
+
{
|
|
200
|
+
start: "2026-06-11T10:00:00+02:00",
|
|
201
|
+
end: "2026-06-11T11:00:00+02:00",
|
|
202
|
+
},
|
|
203
|
+
),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const patched = (http?.calls ?? []).find((call) => call.method === "PATCH");
|
|
207
|
+
assert.ok(patched, "the edit was sent");
|
|
208
|
+
assert.ok(patched.url.includes("scope=All"), patched.url);
|
|
209
|
+
assert.ok(
|
|
210
|
+
patched.url.includes(encodeURIComponent("2026-06-11T07:15:00Z")),
|
|
211
|
+
"the server moves the series by what changed on this occurrence",
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
194
215
|
it("makes the delete conditional on the same version", async () => {
|
|
195
216
|
await mount(() => ({ items: [] }));
|
|
196
217
|
await write((writes) => writes.deleteEvent(scopedWrite));
|
|
@@ -95,10 +95,12 @@ export function useInvalidateCalendarReads(): () => void {
|
|
|
95
95
|
}, [queryClient]);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* The occurrence rides along under `All` too: an edit's times are the times of
|
|
100
|
+
* the occurrence it was made from, and the server moves the series by as much.
|
|
101
|
+
*/
|
|
98
102
|
const scopeQuery = ({ scope, recurrenceId }: ScopedWrite) =>
|
|
99
|
-
scope === undefined
|
|
100
|
-
? {}
|
|
101
|
-
: { scope: SCOPES[scope], recurrenceId };
|
|
103
|
+
scope === undefined ? {} : { scope: SCOPES[scope], recurrenceId };
|
|
102
104
|
|
|
103
105
|
export function useCalendarWrites(): CalendarWrites {
|
|
104
106
|
const invalidate = useInvalidateCalendarReads();
|