@indietabletop/appkit 5.0.0-0 → 5.0.0-2

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.
@@ -1,24 +1,20 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
1
  import { fn } from "storybook/test";
2
+ import preview from "../../.storybook/preview.tsx";
3
3
  import { AuthCard } from "./AuthCard.tsx";
4
4
 
5
- const meta = {
5
+ const meta = preview.meta({
6
6
  title: "Account/Auth Card",
7
7
  component: AuthCard,
8
8
  tags: ["autodocs"],
9
9
  args: {
10
10
  onLogout: fn(),
11
11
  },
12
- } satisfies Meta<typeof AuthCard>;
13
-
14
- export default meta;
15
-
16
- type Story = StoryObj<typeof meta>;
12
+ });
17
13
 
18
14
  /**
19
15
  * The default case where elements are correctly separated with middots.
20
16
  */
21
- export const Authenticated: Story = {
17
+ export const Authenticated = meta.story({
22
18
  args: {
23
19
  currentUser: {
24
20
  id: "martin",
@@ -26,13 +22,13 @@ export const Authenticated: Story = {
26
22
  isVerified: true,
27
23
  },
28
24
  },
29
- };
25
+ });
30
26
 
31
27
  /**
32
28
  * The default case in which all steps of the flow succeed.
33
29
  */
34
- export const Anonymous: Story = {
30
+ export const Anonymous = meta.story({
35
31
  args: {
36
32
  currentUser: null,
37
33
  },
38
- };
34
+ });
@@ -1,5 +1,5 @@
1
1
  import { FormProvider } from "@ariakit/react";
2
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import preview from "../../.storybook/preview.tsx";
3
3
  import {
4
4
  Letterhead,
5
5
  LetterheadHeading,
@@ -7,20 +7,16 @@ import {
7
7
  LetterheadSubmitButton,
8
8
  } from "./index.tsx";
9
9
 
10
- const meta = {
11
- title: "Letterhead",
10
+ const meta = preview.meta({
11
+ title: "Components/Letterhead",
12
12
  component: Letterhead,
13
13
  tags: ["autodocs"],
14
14
  args: {
15
15
  textAlign: "start",
16
16
  },
17
- } satisfies Meta<typeof Letterhead>;
17
+ });
18
18
 
19
- export default meta;
20
-
21
- type Story = StoryObj<typeof meta>;
22
-
23
- export const Default: Story = {
19
+ export const Default = meta.story({
24
20
  args: {
25
21
  children: (
26
22
  <>
@@ -42,4 +38,4 @@ export const Default: Story = {
42
38
  </>
43
39
  ),
44
40
  },
45
- };
41
+ });
@@ -1,21 +1,17 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
1
+ import preview from "../../.storybook/preview.tsx";
2
2
  import { LetterheadReadonlyTextField } from "./index.tsx";
3
3
 
4
- const meta = {
5
- title: "ReadonlyTextField",
4
+ const meta = preview.meta({
5
+ title: "Components/ReadonlyTextField",
6
6
  component: LetterheadReadonlyTextField,
7
7
  tags: ["autodocs"],
8
8
  args: {},
9
- } satisfies Meta<typeof LetterheadReadonlyTextField>;
9
+ });
10
10
 
11
- export default meta;
12
-
13
- type Story = StoryObj<typeof meta>;
14
-
15
- export const Default: Story = {
11
+ export const Default = meta.story({
16
12
  args: {
17
13
  label: "Email",
18
14
  value: "john@example.com",
19
15
  placeholder: "john@example.com",
20
16
  },
21
- };
17
+ });
@@ -1,23 +1,19 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
1
+ import preview from "../../.storybook/preview.tsx";
2
2
  import { form } from "../storybook/decorators.tsx";
3
3
  import { LetterheadSubmitError } from "./index.tsx";
4
4
 
5
- const meta = {
6
- title: "LetterheadSubmitError",
5
+ const meta = preview.meta({
6
+ title: "Components/LetterheadSubmitError",
7
7
  component: LetterheadSubmitError,
8
8
  tags: ["autodocs"],
9
9
  args: {},
10
10
  decorators: [
11
11
  form({ defaultErrors: { submit: "This is an error message." } }),
12
12
  ],
13
- } satisfies Meta<typeof LetterheadSubmitError>;
13
+ });
14
14
 
15
- export default meta;
16
-
17
- type Story = StoryObj<typeof meta>;
18
-
19
- export const Default: Story = {
15
+ export const Default = meta.story({
20
16
  args: {
21
17
  name: "submit",
22
18
  },
23
- };
19
+ });
@@ -1,23 +1,19 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
1
+ import preview from "../../.storybook/preview.tsx";
2
2
  import { form } from "../storybook/decorators.tsx";
3
3
  import { LetterheadTextField } from "./index.tsx";
4
4
 
5
- const meta = {
6
- title: "FormTextField",
5
+ const meta = preview.meta({
6
+ title: "Components/LetterheadTextField",
7
7
  component: LetterheadTextField,
8
8
  tags: ["autodocs"],
9
9
  args: {},
10
10
  decorators: [form()],
11
- } satisfies Meta<typeof LetterheadTextField>;
11
+ });
12
12
 
13
- export default meta;
14
-
15
- type Story = StoryObj<typeof meta>;
16
-
17
- export const Default: Story = {
13
+ export const Default = meta.story({
18
14
  args: {
19
15
  label: "Email",
20
16
  placeholder: "john@example.com",
21
17
  name: "foo",
22
18
  },
23
- };
19
+ });
@@ -1,32 +1,26 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
1
+ import preview from "../../.storybook/preview.tsx";
2
2
  import { MiddotSeparated } from "./MiddotSeparated.tsx";
3
3
 
4
- type ComponentType = typeof MiddotSeparated;
5
-
6
- type Story = StoryObj<typeof meta>;
7
-
8
- const meta = {
4
+ const meta = preview.meta({
9
5
  title: "Components/Middot Separated",
10
6
  component: MiddotSeparated,
11
7
  tags: ["autodocs"],
12
- } satisfies Meta<ComponentType>;
13
-
14
- export default meta;
8
+ });
15
9
 
16
10
  /**
17
11
  * The default case in which all steps of the flow succeed.
18
12
  */
19
- export const Default: Story = {
13
+ export const Default = meta.story({
20
14
  args: {
21
15
  children: ["Lorem", "Ipsum", " Dolor"],
22
16
  },
23
- };
17
+ });
24
18
 
25
19
  /**
26
20
  * Edge case when it comes to handling children in React.
27
21
  */
28
- export const SingleElement: Story = {
22
+ export const SingleElement = meta.story({
29
23
  args: {
30
24
  children: "Lorem",
31
25
  },
32
- };
26
+ });
@@ -1,13 +1,9 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
1
  import { http, HttpResponse } from "msw";
2
+ import preview from "../../.storybook/preview.tsx";
3
3
  import { Failure, Pending, Success } from "../async-op.ts";
4
4
  import { QRCode } from "./QRCode.tsx";
5
5
 
6
- type StoryMeta = Meta<typeof QRCode>;
7
-
8
- type Story = StoryObj<typeof meta>;
9
-
10
- const meta = {
6
+ const meta = preview.meta({
11
7
  title: "Components/QR Code",
12
8
  component: QRCode,
13
9
  tags: ["autodocs"],
@@ -28,20 +24,18 @@ const meta = {
28
24
  },
29
25
  },
30
26
  },
31
- } satisfies StoryMeta;
32
-
33
- export default meta;
27
+ });
34
28
 
35
- export const SuccessCase: Story = {};
29
+ export const SuccessCase = meta.story({});
36
30
 
37
- export const PendingCase: Story = {
31
+ export const PendingCase = meta.story({
38
32
  args: {
39
33
  url: new Pending(),
40
34
  },
41
- };
35
+ });
42
36
 
43
- export const FailureCase: Story = {
37
+ export const FailureCase = meta.story({
44
38
  args: {
45
39
  url: new Failure("Failed"),
46
40
  },
47
- };
41
+ });
@@ -4,7 +4,7 @@ import {
4
4
  LetterheadParagraph,
5
5
  } from "../Letterhead/index.tsx";
6
6
  import * as css from "./style.css.ts";
7
- import type { ViewContent } from "./SubscribeCard.tsx";
7
+ import type { ViewContent } from "./SubscribeByPledgeCard.tsx";
8
8
 
9
9
  export function LetterheadInfoCard(props: ViewContent) {
10
10
  return (
@@ -1,5 +1,5 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
1
  import { http, HttpResponse } from "msw";
2
+ import preview from "../../.storybook/preview.tsx";
3
3
  import { LetterheadParagraph } from "../Letterhead/index.tsx";
4
4
  import { sleep } from "../sleep.ts";
5
5
  import { SubscribeByEmailCard } from "./SubscribeByEmailCard.tsx";
@@ -30,12 +30,8 @@ const handlers = {
30
30
  },
31
31
  };
32
32
 
33
- /**
34
- * Allows users to subscribe to ITC's newsletter using the email provided
35
- * in their pledge.
36
- */
37
- const meta = {
38
- title: "Pages/Subscribe By Email Card",
33
+ const meta = preview.meta({
34
+ title: "Newsletter/Subscribe By Email Card",
39
35
  component: SubscribeByEmailCard,
40
36
  tags: ["autodocs"],
41
37
  args: {
@@ -68,10 +64,6 @@ const meta = {
68
64
  },
69
65
  },
70
66
  },
71
- } satisfies Meta<typeof SubscribeByEmailCard>;
67
+ });
72
68
 
73
- export default meta;
74
-
75
- type Story = StoryObj<typeof meta>;
76
-
77
- export const Default: Story = {};
69
+ export const Default = meta.story({});
@@ -149,6 +149,12 @@ type ViewContent = {
149
149
  description: ReactNode;
150
150
  };
151
151
 
152
+ /**
153
+ * Allows users to sign up to the newsletter using an arbitrary email.
154
+ *
155
+ * Signups are verified using an one-time code, which is sent to the supplied
156
+ * email address.
157
+ */
152
158
  export function SubscribeByEmailCard(props: {
153
159
  content: Record<Exclude<SubscribeStep["type"], "SUBMIT_CODE">, ViewContent>;
154
160
  }) {
@@ -1,8 +1,8 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
1
  import { http, HttpResponse } from "msw";
2
+ import preview from "../../.storybook/preview.tsx";
3
3
  import { LetterheadParagraph } from "../Letterhead/index.tsx";
4
4
  import { sleep } from "../sleep.ts";
5
- import { SubscribeCard } from "./SubscribeCard.tsx";
5
+ import { SubscribeByPledgeCard } from "./SubscribeByPledgeCard.tsx";
6
6
 
7
7
  const pledge = {
8
8
  id: "1",
@@ -48,9 +48,9 @@ const subscribeByPledgeId = {
48
48
  * Allows users to subscribe to ITC's newsletter using the email provided
49
49
  * in their pledge.
50
50
  */
51
- const meta = {
52
- title: "Pages/Subscribe Card",
53
- component: SubscribeCard,
51
+ const meta = preview.meta({
52
+ title: "Newsletter/Subscribe By Pledge Card",
53
+ component: SubscribeByPledgeCard,
54
54
  tags: ["autodocs"],
55
55
  args: {
56
56
  pledge,
@@ -101,24 +101,20 @@ const meta = {
101
101
  },
102
102
  },
103
103
  },
104
- } satisfies Meta<typeof SubscribeCard>;
105
-
106
- export default meta;
107
-
108
- type Story = StoryObj<typeof meta>;
104
+ });
109
105
 
110
106
  /**
111
107
  * The default case, in which the user is not yet subscribed to ITC's newsletter
112
108
  * and the submission is successful.
113
109
  */
114
- export const Default: Story = {};
110
+ export const Default = meta.story({});
115
111
 
116
112
  /**
117
113
  * Same like the default case, but the form submission fails. Errors are
118
114
  * differentiated using the `getSubmitFailureMessage` helper, so all the basics
119
115
  * should be covered, as this page doesn't have special cases.
120
116
  */
121
- export const FailureOnSubmit: Story = {
117
+ export const FailureOnSubmit = meta.story({
122
118
  parameters: {
123
119
  msw: {
124
120
  handlers: {
@@ -126,12 +122,12 @@ export const FailureOnSubmit: Story = {
126
122
  },
127
123
  },
128
124
  },
129
- };
125
+ });
130
126
 
131
127
  /**
132
128
  * In this case, the email address associated with this user account is
133
129
  * already subscribed to our newsletter.
134
130
  */
135
- export const AlreadySubscribed: Story = {
131
+ export const AlreadySubscribed = meta.story({
136
132
  args: { pledge: { ...pledge, contactSubscribed: true } },
137
- };
133
+ });
@@ -69,7 +69,15 @@ export type ViewContent = {
69
69
  description: ReactNode;
70
70
  };
71
71
 
72
- export function SubscribeCard(props: {
72
+ /**
73
+ * Allows users to sign up to the newsletter using the email associated with
74
+ * their pledge ID.
75
+ *
76
+ * Emails are not verified using a one-time code, as the user has just
77
+ * implicitly verified their address by navigating to the page with the unique
78
+ * pledge ID.
79
+ */
80
+ export function SubscribeByPledgeCard(props: {
73
81
  /**
74
82
  * The pledge data that will be used to determine whether the user should
75
83
  * be prompted for signup.
@@ -110,3 +118,10 @@ export function SubscribeCard(props: {
110
118
  }
111
119
  }
112
120
  }
121
+
122
+ export {
123
+ /**
124
+ * @deprecated Use {@link SubscribeByPledgeCard}
125
+ */
126
+ SubscribeByPledgeCard as SubscribeCard,
127
+ };
@@ -1,6 +1,5 @@
1
- import { Story } from "@storybook/addon-docs/blocks";
2
- import type { Meta, StoryObj } from "@storybook/react-vite";
3
1
  import { http, HttpResponse } from "msw";
2
+ import preview from "../../.storybook/preview.tsx";
4
3
  import { sleep } from "../sleep.ts";
5
4
  import type { CurrentUser, SessionInfo } from "../types.ts";
6
5
  import { CurrentUserFetcher } from "./CurrentUserFetcher.tsx";
@@ -162,7 +161,7 @@ function createMocks(options?: { responseSpeed?: number }) {
162
161
 
163
162
  const { data, handlers } = createMocks({ responseSpeed: 700 });
164
163
 
165
- const meta = {
164
+ const meta = preview.meta({
166
165
  title: "Account/Current User Fetcher",
167
166
  component: CurrentUserFetcher,
168
167
  tags: ["autodocs"],
@@ -189,18 +188,14 @@ const meta = {
189
188
  },
190
189
  },
191
190
  },
192
- } satisfies Meta<typeof CurrentUserFetcher>;
193
-
194
- export default meta;
195
-
196
- type Story = StoryObj<typeof meta>;
191
+ });
197
192
 
198
193
  /**
199
194
  * The default case, in which a local user is provided (so they have previously
200
195
  * logged in), and the current user request returns the same user as is
201
196
  * the local user (determined by the user id).
202
197
  */
203
- export const Default: Story = {
198
+ export const Default = meta.story({
204
199
  parameters: {
205
200
  msw: {
206
201
  handlers: {
@@ -208,19 +203,19 @@ export const Default: Story = {
208
203
  },
209
204
  },
210
205
  },
211
- };
206
+ });
212
207
 
213
208
  /**
214
209
  * In this case, no local user is provided and the component simply renders
215
210
  * its children. There should be no network request in this case.
216
211
  */
217
- export const NoLocalUser: Story = {};
212
+ export const NoLocalUser = meta.story({});
218
213
 
219
214
  /**
220
215
  * In this case, the local user is provided and the current user request returns
221
216
  * error 401. The user should supply their credentials again.
222
217
  */
223
- export const SessionExpired: Story = {
218
+ export const SessionExpired = meta.story({
224
219
  parameters: {
225
220
  msw: {
226
221
  handlers: {
@@ -228,7 +223,7 @@ export const SessionExpired: Story = {
228
223
  },
229
224
  },
230
225
  },
231
- };
226
+ });
232
227
 
233
228
  /**
234
229
  * In this case, the local user is provided and the current user request returns
@@ -241,7 +236,7 @@ export const SessionExpired: Story = {
241
236
  * In practice this should be a rare case, but with unpleasant circumstances
242
237
  * if not handled correctly.
243
238
  */
244
- export const UserMismatch: Story = {
239
+ export const UserMismatch = meta.story({
245
240
  parameters: {
246
241
  msw: {
247
242
  handlers: {
@@ -249,11 +244,11 @@ export const UserMismatch: Story = {
249
244
  },
250
245
  },
251
246
  },
252
- };
247
+ });
253
248
 
254
249
  /**
255
250
  */
256
- export const UserUnverified: Story = {
251
+ export const UserUnverified = meta.story({
257
252
  parameters: {
258
253
  msw: {
259
254
  handlers: {
@@ -264,7 +259,7 @@ export const UserUnverified: Story = {
264
259
  },
265
260
  },
266
261
  },
267
- };
262
+ });
268
263
 
269
264
  /**
270
265
  * In this case, the local user is provided and the current user request returns
@@ -272,7 +267,7 @@ export const UserUnverified: Story = {
272
267
  *
273
268
  * This can happen if users delete their accounts but
274
269
  */
275
- export const UserNotFound: Story = {
270
+ export const UserNotFound = meta.story({
276
271
  parameters: {
277
272
  msw: {
278
273
  handlers: {
@@ -280,13 +275,13 @@ export const UserNotFound: Story = {
280
275
  },
281
276
  },
282
277
  },
283
- };
278
+ });
284
279
 
285
280
  /**
286
281
  * The proactive user session check has failed due to an error that doesn't
287
282
  * carry any special meaning.
288
283
  */
289
- export const UnknownFailure: Story = {
284
+ export const UnknownFailure = meta.story({
290
285
  parameters: {
291
286
  msw: {
292
287
  handlers: {
@@ -294,4 +289,4 @@ export const UnknownFailure: Story = {
294
289
  },
295
290
  },
296
291
  },
297
- };
292
+ });
@@ -34,6 +34,10 @@ export function CurrentUserFetcher(props: CurrentUserFetcherProps) {
34
34
  // We only want to fetch the current user if they exist in the store, i.e.
35
35
  // they have logged into the app previously.
36
36
  performFetch: !!localUser,
37
+
38
+ // If we are performing a fetch, we want to make sure that it is up to date
39
+ // every time the user focuses the window.
40
+ revalidateOnFocus: true,
37
41
  });
38
42
 
39
43
  if (result.isFailure && result.failure.type === "API_ERROR") {
@@ -1,6 +1,5 @@
1
- import { Story } from "@storybook/addon-docs/blocks";
2
- import type { Meta, StoryObj } from "@storybook/react-vite";
3
1
  import { http, HttpResponse } from "msw";
2
+ import preview from "../../.storybook/preview.tsx";
4
3
  import { sleep } from "../sleep.ts";
5
4
  import type { CurrentUser, SessionInfo } from "../types.ts";
6
5
  import { JoinCard } from "./JoinCard.tsx";
@@ -135,7 +134,7 @@ function createMocks(options?: { responseSpeed?: number }) {
135
134
 
136
135
  const { data, handlers } = createMocks({ responseSpeed: 700 });
137
136
 
138
- const meta = {
137
+ const meta = preview.meta({
139
138
  title: "Account/Join Card",
140
139
  component: JoinCard,
141
140
  tags: ["autodocs"],
@@ -152,22 +151,18 @@ const meta = {
152
151
  },
153
152
  },
154
153
  },
155
- } satisfies Meta<typeof JoinCard>;
156
-
157
- export default meta;
158
-
159
- type Story = StoryObj<typeof meta>;
154
+ });
160
155
 
161
156
  /**
162
157
  * The default case in which all steps of the flow succeed.
163
158
  */
164
- export const Success: Story = {};
159
+ export const Success = meta.story({});
165
160
 
166
161
  /**
167
162
  * In this case, the initial step fails because the email address is associated
168
163
  * with an existing user.
169
164
  */
170
- export const EmailTakenOnJoin: Story = {
165
+ export const EmailTakenOnJoin = meta.story({
171
166
  parameters: {
172
167
  msw: {
173
168
  handlers: {
@@ -175,13 +170,13 @@ export const EmailTakenOnJoin: Story = {
175
170
  },
176
171
  },
177
172
  },
178
- };
173
+ });
179
174
 
180
175
  /**
181
176
  * In this case, the initial step fails for a reason that doesn't have any
182
177
  * special handling in this location. E.g. network error, server error, etc.
183
178
  */
184
- export const UnknownFailureOnJoin: Story = {
179
+ export const UnknownFailureOnJoin = meta.story({
185
180
  parameters: {
186
181
  msw: {
187
182
  handlers: {
@@ -189,13 +184,13 @@ export const UnknownFailureOnJoin: Story = {
189
184
  },
190
185
  },
191
186
  },
192
- };
187
+ });
193
188
 
194
189
  /**
195
190
  * In this case, the verify step fails because the token has expired,
196
191
  * or is incorrect.
197
192
  */
198
- export const NotFoundOrExpiredOnVerify: Story = {
193
+ export const NotFoundOrExpiredOnVerify = meta.story({
199
194
  parameters: {
200
195
  msw: {
201
196
  handlers: {
@@ -203,13 +198,13 @@ export const NotFoundOrExpiredOnVerify: Story = {
203
198
  },
204
199
  },
205
200
  },
206
- };
201
+ });
207
202
 
208
203
  /**
209
204
  * In this case, the verify step fails for a reason that doesn't have any
210
205
  * special handling in this location. E.g. network error, server error, etc.
211
206
  */
212
- export const UnknownFailureOnVerify: Story = {
207
+ export const UnknownFailureOnVerify = meta.story({
213
208
  parameters: {
214
209
  msw: {
215
210
  handlers: {
@@ -217,14 +212,14 @@ export const UnknownFailureOnVerify: Story = {
217
212
  },
218
213
  },
219
214
  },
220
- };
215
+ });
221
216
 
222
217
  /**
223
218
  * The proactive user-session check returns a user account.
224
219
  *
225
220
  * The user can continue to use the app, or log out.
226
221
  */
227
- export const AlreadyLoggedIn: Story = {
222
+ export const AlreadyLoggedIn = meta.story({
228
223
  parameters: {
229
224
  msw: {
230
225
  handlers: {
@@ -232,12 +227,12 @@ export const AlreadyLoggedIn: Story = {
232
227
  },
233
228
  },
234
229
  },
235
- };
230
+ });
236
231
 
237
232
  /**
238
233
  * The proactive user session check has failed due to connection issues.
239
234
  */
240
- export const NoConnection: Story = {
235
+ export const NoConnection = meta.story({
241
236
  parameters: {
242
237
  msw: {
243
238
  handlers: {
@@ -245,13 +240,13 @@ export const NoConnection: Story = {
245
240
  },
246
241
  },
247
242
  },
248
- };
243
+ });
249
244
 
250
245
  /**
251
246
  * The proactive user session check has failed due to an error that doesn't
252
247
  * carry any special meaning.
253
248
  */
254
- export const UnknownFailure: Story = {
249
+ export const UnknownFailure = meta.story({
255
250
  parameters: {
256
251
  msw: {
257
252
  handlers: {
@@ -259,4 +254,4 @@ export const UnknownFailure: Story = {
259
254
  },
260
255
  },
261
256
  },
262
- };
257
+ });
@@ -1,7 +1,6 @@
1
- import { Story } from "@storybook/addon-docs/blocks";
2
- import type { Meta, StoryObj } from "@storybook/react-vite";
3
1
  import { http, HttpResponse } from "msw";
4
2
  import { fn } from "storybook/test";
3
+ import preview from "../../.storybook/preview.tsx";
5
4
  import { sleep } from "../sleep.ts";
6
5
  import type { CurrentUser, SessionInfo } from "../types.ts";
7
6
  import { LoginCard } from "./LoginCard.tsx";
@@ -123,7 +122,7 @@ function createMocks(options?: { responseSpeed?: number }) {
123
122
 
124
123
  const { data, handlers } = createMocks({ responseSpeed: 700 });
125
124
 
126
- const meta = {
125
+ const meta = preview.meta({
127
126
  title: "Account/Login Card",
128
127
  component: LoginCard,
129
128
  tags: ["autodocs"],
@@ -139,17 +138,13 @@ const meta = {
139
138
  },
140
139
  },
141
140
  },
142
- } satisfies Meta<typeof LoginCard>;
143
-
144
- export default meta;
145
-
146
- type Story = StoryObj<typeof meta>;
141
+ });
147
142
 
148
143
  /**
149
144
  * The majority case where no user is stored locally, proactive user session
150
145
  * check returns 401, and subsequently correct credentials are provided.
151
146
  */
152
- export const Default: Story = {
147
+ export const Default = meta.story({
153
148
  parameters: {
154
149
  msw: {
155
150
  handlers: {
@@ -158,13 +153,13 @@ export const Default: Story = {
158
153
  },
159
154
  },
160
155
  },
161
- };
156
+ });
162
157
 
163
158
  /**
164
159
  * Similar to the default case, but invalid credentials are provided to when
165
160
  * attempting to create a new session.
166
161
  */
167
- export const InvalidCredentialsOnSubmit: Story = {
162
+ export const InvalidCredentialsOnSubmit = meta.story({
168
163
  parameters: {
169
164
  msw: {
170
165
  handlers: {
@@ -173,13 +168,13 @@ export const InvalidCredentialsOnSubmit: Story = {
173
168
  },
174
169
  },
175
170
  },
176
- };
171
+ });
177
172
 
178
173
  /**
179
174
  * Similar to the default case, but when credentials are provided, an account
180
175
  * email is used that is not currently in the database.
181
176
  */
182
- export const UserNotFoundOnSubmit: Story = {
177
+ export const UserNotFoundOnSubmit = meta.story({
183
178
  parameters: {
184
179
  msw: {
185
180
  handlers: {
@@ -188,13 +183,13 @@ export const UserNotFoundOnSubmit: Story = {
188
183
  },
189
184
  },
190
185
  },
191
- };
186
+ });
192
187
 
193
188
  /**
194
189
  * Similar to the default case, but the session creation call returns an error
195
190
  * that doesn't have a specific meaning in the context of the login page.
196
191
  */
197
- export const UnknownFailureOnSubmit: Story = {
192
+ export const UnknownFailureOnSubmit = meta.story({
198
193
  parameters: {
199
194
  msw: {
200
195
  handlers: {
@@ -203,12 +198,12 @@ export const UnknownFailureOnSubmit: Story = {
203
198
  },
204
199
  },
205
200
  },
206
- };
201
+ });
207
202
 
208
203
  /**
209
204
  * A case when user is stored locally, but their server session has expired.
210
205
  */
211
- export const ReauthenticateSession: Story = {
206
+ export const ReauthenticateSession = meta.story({
212
207
  parameters: {
213
208
  msw: {
214
209
  handlers: {
@@ -217,7 +212,7 @@ export const ReauthenticateSession: Story = {
217
212
  },
218
213
  },
219
214
  },
220
- };
215
+ });
221
216
 
222
217
  /**
223
218
  * The user is already stored in the app, and proactive user-session check
@@ -225,7 +220,7 @@ export const ReauthenticateSession: Story = {
225
220
  *
226
221
  * The user is directed to app without any further steps.
227
222
  */
228
- export const AlreadyLoggedIn: Story = {
223
+ export const AlreadyLoggedIn = meta.story({
229
224
  parameters: {
230
225
  msw: {
231
226
  handlers: {
@@ -233,14 +228,14 @@ export const AlreadyLoggedIn: Story = {
233
228
  },
234
229
  },
235
230
  },
236
- };
231
+ });
237
232
 
238
233
  /**
239
234
  * A user is provided, and proactive user session check returns a different
240
235
  * user. This can happen when different users log into separate apps with
241
236
  * different credentials.
242
237
  */
243
- export const UserMismatch: Story = {
238
+ export const UserMismatch = meta.story({
244
239
  parameters: {
245
240
  msw: {
246
241
  handlers: {
@@ -248,14 +243,14 @@ export const UserMismatch: Story = {
248
243
  },
249
244
  },
250
245
  },
251
- };
246
+ });
252
247
 
253
248
  /**
254
249
  * During the proactive user session check, the user is reportd as not found.
255
250
  * This means that the tokens are still valid, but the user DB entity is gone.
256
251
  * This can happen if a user closed their account.
257
252
  */
258
- export const UserNotFound: Story = {
253
+ export const UserNotFound = meta.story({
259
254
  parameters: {
260
255
  msw: {
261
256
  handlers: {
@@ -263,12 +258,12 @@ export const UserNotFound: Story = {
263
258
  },
264
259
  },
265
260
  },
266
- };
261
+ });
267
262
 
268
263
  /**
269
264
  * The proactive user session check has failed due to connection issues.
270
265
  */
271
- export const NoConnection: Story = {
266
+ export const NoConnection = meta.story({
272
267
  parameters: {
273
268
  msw: {
274
269
  handlers: {
@@ -276,13 +271,13 @@ export const NoConnection: Story = {
276
271
  },
277
272
  },
278
273
  },
279
- };
274
+ });
280
275
 
281
276
  /**
282
277
  * The proactive user session check has failed due to an error that doesn't
283
278
  * carry any special meaning.
284
279
  */
285
- export const UnknownFailure: Story = {
280
+ export const UnknownFailure = meta.story({
286
281
  parameters: {
287
282
  msw: {
288
283
  handlers: {
@@ -290,4 +285,4 @@ export const UnknownFailure: Story = {
290
285
  },
291
286
  },
292
287
  },
293
- };
288
+ });
@@ -1,6 +1,5 @@
1
- import { Story } from "@storybook/addon-docs/blocks";
2
- import type { Meta, StoryObj } from "@storybook/react-vite";
3
1
  import { http, HttpResponse } from "msw";
2
+ import preview from "../../.storybook/preview.tsx";
4
3
  import { sleep } from "../sleep.ts";
5
4
  import type { CurrentUser, SessionInfo } from "../types.ts";
6
5
  import { PasswordResetCard } from "./PasswordResetCard.tsx";
@@ -132,7 +131,7 @@ function createMocks(options?: { responseSpeed?: number }) {
132
131
 
133
132
  const { data, handlers } = createMocks({ responseSpeed: 700 });
134
133
 
135
- const meta = {
134
+ const meta = preview.meta({
136
135
  title: "Account/Password Reset Page",
137
136
  component: PasswordResetCard,
138
137
  tags: ["autodocs"],
@@ -147,22 +146,18 @@ const meta = {
147
146
  },
148
147
  },
149
148
  },
150
- } satisfies Meta<typeof PasswordResetCard>;
151
-
152
- export default meta;
153
-
154
- type Story = StoryObj<typeof meta>;
149
+ });
155
150
 
156
151
  /**
157
152
  * The default case in which all steps of the flow succeed.
158
153
  */
159
- export const Success: Story = {};
154
+ export const Success = meta.story({});
160
155
 
161
156
  /**
162
157
  * In this case, the initial step fails because the email address was not
163
158
  * found in our database.
164
159
  */
165
- export const UserNotFoundOnRequestCode: Story = {
160
+ export const UserNotFoundOnRequestCode = meta.story({
166
161
  parameters: {
167
162
  msw: {
168
163
  handlers: {
@@ -170,13 +165,13 @@ export const UserNotFoundOnRequestCode: Story = {
170
165
  },
171
166
  },
172
167
  },
173
- };
168
+ });
174
169
 
175
170
  /**
176
171
  * In this case, the initial step fails for a reason that doesn't have any
177
172
  * special handling in this location. E.g. network error, server error, etc.
178
173
  */
179
- export const UnknownFailureOnRequestCode: Story = {
174
+ export const UnknownFailureOnRequestCode = meta.story({
180
175
  parameters: {
181
176
  msw: {
182
177
  handlers: {
@@ -184,13 +179,13 @@ export const UnknownFailureOnRequestCode: Story = {
184
179
  },
185
180
  },
186
181
  },
187
- };
182
+ });
188
183
 
189
184
  /**
190
185
  * In this case, the check code step fails because the token has expired,
191
186
  * or is incorrect.
192
187
  */
193
- export const NotFoundOrExpiredOnCheckCode: Story = {
188
+ export const NotFoundOrExpiredOnCheckCode = meta.story({
194
189
  parameters: {
195
190
  msw: {
196
191
  handlers: {
@@ -198,13 +193,13 @@ export const NotFoundOrExpiredOnCheckCode: Story = {
198
193
  },
199
194
  },
200
195
  },
201
- };
196
+ });
202
197
 
203
198
  /**
204
199
  * In this case, the check step fails for a reason that doesn't have any
205
200
  * special handling in this location. E.g. network error, server error, etc.
206
201
  */
207
- export const UnknownFailureOnCheckCode: Story = {
202
+ export const UnknownFailureOnCheckCode = meta.story({
208
203
  parameters: {
209
204
  msw: {
210
205
  handlers: {
@@ -212,7 +207,7 @@ export const UnknownFailureOnCheckCode: Story = {
212
207
  },
213
208
  },
214
209
  },
215
- };
210
+ });
216
211
 
217
212
  /**
218
213
  * In this case, the final set password step fails because the token has
@@ -222,7 +217,7 @@ export const UnknownFailureOnCheckCode: Story = {
222
217
  * because the user has gotten through the check step, that would require
223
218
  * some very strange set of circumstances.
224
219
  */
225
- export const NotFoundOrExpiredOnSetPassword: Story = {
220
+ export const NotFoundOrExpiredOnSetPassword = meta.story({
226
221
  parameters: {
227
222
  msw: {
228
223
  handlers: {
@@ -230,13 +225,13 @@ export const NotFoundOrExpiredOnSetPassword: Story = {
230
225
  },
231
226
  },
232
227
  },
233
- };
228
+ });
234
229
 
235
230
  /**
236
231
  * In this case, the set password step fails for a reason that doesn't have any
237
232
  * special handling in this location. E.g. network error, server error, etc.
238
233
  */
239
- export const UnknownFailureOnSetPassword: Story = {
234
+ export const UnknownFailureOnSetPassword = meta.story({
240
235
  parameters: {
241
236
  msw: {
242
237
  handlers: {
@@ -244,4 +239,4 @@ export const UnknownFailureOnSetPassword: Story = {
244
239
  },
245
240
  },
246
241
  },
247
- };
242
+ });
@@ -10,8 +10,18 @@ export function useFetchCurrentUser(options?: {
10
10
  * @default true
11
11
  */
12
12
  performFetch?: boolean;
13
+
14
+ /**
15
+ * Fetch new data every time the window is focused.
16
+ *
17
+ * Note that `performFetch` has to be enabled for this option to have an
18
+ * effect — we are not revalidating when there is no fetch to do!
19
+ *
20
+ * @default false
21
+ */
22
+ revalidateOnFocus?: boolean;
13
23
  }) {
14
- const { performFetch = true } = options ?? {};
24
+ const { performFetch = true, revalidateOnFocus = false } = options ?? {};
15
25
  const client = useClient();
16
26
  const [latestAttemptTs, setLatestAttemptTs] = useState(Date.now());
17
27
 
@@ -31,12 +41,14 @@ export function useFetchCurrentUser(options?: {
31
41
  // Invoke the fetch action
32
42
  fetchUserAndStoreResult();
33
43
 
34
- // Set up listeners for data revalidation
35
- window.addEventListener("focus", fetchUserAndStoreResult);
44
+ if (revalidateOnFocus) {
45
+ const controller = new AbortController();
46
+ window.addEventListener("focus", fetchUserAndStoreResult, controller);
36
47
 
37
- return () => {
38
- window.removeEventListener("focus", fetchUserAndStoreResult);
39
- };
48
+ return () => {
49
+ controller.abort();
50
+ };
51
+ }
40
52
  }
41
53
  }, [client, latestAttemptTs, performFetch]);
42
54
 
package/lib/client.ts CHANGED
@@ -545,7 +545,7 @@ export class IndieTabletopClient {
545
545
  });
546
546
 
547
547
  return await this.fetch(
548
- `/v1/me/data?${params}`,
548
+ `/v1/me/game-data?${params}`,
549
549
  unknown() as Struct<UserGameData, null>,
550
550
  );
551
551
  }
@@ -557,7 +557,7 @@ export class IndieTabletopClient {
557
557
  expectCurrentUserId: string | null | undefined;
558
558
  }) {
559
559
  return await this.fetch(
560
- `/v1/me/data`,
560
+ `/v1/me/game-data`,
561
561
  unknown() as Struct<UserGameData, null>,
562
562
  {
563
563
  method: "PATCH",
package/lib/index.ts CHANGED
@@ -23,7 +23,7 @@ export * from "./ReleaseInfo/index.tsx";
23
23
  export * from "./ServiceWorkerHandler.tsx";
24
24
  export * from "./ShareButton/ShareButton.tsx";
25
25
  export * from "./SubscribeCard/SubscribeByEmailCard.tsx";
26
- export * from "./SubscribeCard/SubscribeCard.tsx";
26
+ export * from "./SubscribeCard/SubscribeByPledgeCard.tsx";
27
27
 
28
28
  // Hooks
29
29
  export * from "./RulesetResolver.ts";
@@ -33,13 +33,17 @@ export function ref() {
33
33
  return object({ id: string() });
34
34
  }
35
35
 
36
+ export function weaponRef() {
37
+ return assign(ref(), object({ count: number() }));
38
+ }
39
+
36
40
  export function treatmentRef() {
37
41
  return assign(ref(), object({ locationId: string() }));
38
42
  }
39
43
 
40
44
  export function vehicleWeaponRef() {
41
45
  return assign(
42
- ref(),
46
+ weaponRef(),
43
47
  object({ placement: enums(["FIXED", "PINTLE_MOUNTED"]) }),
44
48
  );
45
49
  }
@@ -53,7 +57,7 @@ export function gitData() {
53
57
  id: string(),
54
58
  type: ref(),
55
59
  name: string(),
56
- weapons: array(ref()),
60
+ weapons: array(weaponRef()),
57
61
  gear: array(ref()),
58
62
  offenses: array(ref()),
59
63
  treatments: array(treatmentRef()),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indietabletop/appkit",
3
- "version": "5.0.0-0",
3
+ "version": "5.0.0-2",
4
4
  "description": "A collection of modules used in apps built by Indie Tabletop Club",
5
5
  "private": false,
6
6
  "type": "module",
@@ -20,20 +20,23 @@
20
20
  "/lib"
21
21
  ],
22
22
  "author": "Artur Müller",
23
- "repository": "github:indietabletop/appkit",
24
- "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/indietabletop/appkit.git"
26
+ },
27
+ "license": "UNLICENSED",
25
28
  "peerDependencies": {
26
29
  "react": "^18.0.0 || ^19.0.0"
27
30
  },
28
31
  "devDependencies": {
29
- "@storybook/addon-docs": "^9.1.10",
30
- "@storybook/addon-links": "^9.1.10",
31
- "@storybook/react-vite": "^9.1.10",
32
+ "@storybook/addon-docs": "^10.0.4",
33
+ "@storybook/addon-links": "^10.0.4",
34
+ "@storybook/react-vite": "^10.0.4",
32
35
  "@types/react": "^19.1.8",
33
36
  "msw": "^2.11.2",
34
37
  "msw-storybook-addon": "^2.0.5",
35
38
  "np": "^10.1.0",
36
- "storybook": "^9.1.10",
39
+ "storybook": "^10.0.4",
37
40
  "typescript": "^5.8.2",
38
41
  "vite": "^6.3.5",
39
42
  "vitest": "^3.2.4"