@indietabletop/appkit 3.6.0-1 → 3.6.0-3

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.
Files changed (44) hide show
  1. package/lib/AppConfig/AppConfig.tsx +54 -0
  2. package/lib/HistoryState.ts +21 -0
  3. package/lib/Letterhead/style.css.ts +2 -0
  4. package/lib/LetterheadForm/index.tsx +53 -0
  5. package/lib/LetterheadForm/style.css.ts +8 -0
  6. package/lib/QRCode/QRCode.stories.tsx +47 -0
  7. package/lib/QRCode/QRCode.tsx +49 -0
  8. package/lib/QRCode/style.css.ts +19 -0
  9. package/lib/ShareButton/ShareButton.tsx +141 -0
  10. package/lib/SubscribeCard/LetterheadInfoCard.tsx +23 -0
  11. package/lib/SubscribeCard/SubscribeByEmailCard.stories.tsx +83 -0
  12. package/lib/SubscribeCard/SubscribeByEmailCard.tsx +177 -0
  13. package/lib/SubscribeCard/SubscribeCard.stories.tsx +27 -23
  14. package/lib/SubscribeCard/SubscribeCard.tsx +22 -17
  15. package/lib/Title/index.tsx +7 -2
  16. package/lib/account/AccountIssueView.tsx +40 -0
  17. package/lib/account/AlreadyLoggedInView.tsx +44 -0
  18. package/lib/account/CurrentUserFetcher.stories.tsx +339 -0
  19. package/lib/account/CurrentUserFetcher.tsx +119 -0
  20. package/lib/account/FailureFallbackView.tsx +36 -0
  21. package/lib/account/JoinPage.stories.tsx +270 -0
  22. package/lib/account/JoinPage.tsx +288 -0
  23. package/lib/account/LoadingView.tsx +14 -0
  24. package/lib/account/LoginPage.stories.tsx +318 -0
  25. package/lib/account/LoginPage.tsx +138 -0
  26. package/lib/account/LoginView.tsx +136 -0
  27. package/lib/account/NoConnectionView.tsx +34 -0
  28. package/lib/account/PasswordResetPage.stories.tsx +250 -0
  29. package/lib/account/PasswordResetPage.tsx +291 -0
  30. package/lib/account/UserMismatchView.tsx +61 -0
  31. package/lib/account/VerifyPage.tsx +217 -0
  32. package/lib/account/style.css.ts +57 -0
  33. package/lib/account/types.ts +9 -0
  34. package/lib/account/useCurrentUserResult.tsx +38 -0
  35. package/lib/class-names.ts +1 -1
  36. package/lib/client.ts +54 -7
  37. package/lib/globals.css.ts +5 -0
  38. package/lib/index.ts +11 -1
  39. package/lib/useEnsureValue.ts +31 -0
  40. package/package.json +3 -2
  41. package/lib/ClientContext/ClientContext.tsx +0 -25
  42. package/lib/LoginPage/LoginPage.stories.tsx +0 -107
  43. package/lib/LoginPage/LoginPage.tsx +0 -204
  44. package/lib/LoginPage/style.css.ts +0 -17
@@ -1,107 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { http, HttpResponse } from "msw";
3
- import { fn } from "storybook/test";
4
- import { IndieTabletopClient } from "../client.ts";
5
- import { sleep } from "../sleep.ts";
6
- import { LoginPage } from "./LoginPage.tsx";
7
-
8
- const userA = {
9
- id: "a",
10
- email: "a@example.com",
11
- isVerified: true,
12
- };
13
-
14
- const userB = {
15
- id: "b",
16
- email: "b@example.com",
17
- isVerified: true,
18
- };
19
-
20
- const meta = {
21
- title: "Pages/Login Page",
22
- component: LoginPage,
23
- tags: ["autodocs"],
24
- args: {
25
- client: new IndieTabletopClient({ apiOrigin: "" }),
26
- onSuccess: fn(),
27
- onLogout: fn(),
28
- localUser: null,
29
- },
30
- parameters: {
31
- msw: {
32
- handlers: {
33
- getCurrentUser: http.get("/v1/users/me", async () => {
34
- await sleep(2000);
35
-
36
- return HttpResponse.json(userA);
37
- }),
38
-
39
- createNewSession: http.post("/v1/sessions", async () => {
40
- await sleep(2000);
41
-
42
- return HttpResponse.json({
43
- currentUser: userA,
44
- sessionInfo: {
45
- createdTs: 123,
46
- expiresTs: 123,
47
- },
48
- });
49
- }),
50
- },
51
- },
52
- },
53
- } satisfies Meta<typeof LoginPage>;
54
-
55
- export default meta;
56
-
57
- type Story = StoryObj<typeof meta>;
58
-
59
- // There is no local user and server response reports user not authenticated.
60
- // This is the majority case.
61
- export const Default: Story = {
62
- parameters: {
63
- msw: {
64
- handlers: {
65
- getCurrentUser: http.get("/v1/users/me", async () => {
66
- await sleep(2000);
67
-
68
- return HttpResponse.text("Not authenticated", { status: 401 });
69
- }),
70
- },
71
- },
72
- },
73
- };
74
-
75
- // User is logged in via a server cookie. They can proceed to app.
76
- export const AlreadyLoggedIn: Story = {
77
- args: {
78
- localUser: userA,
79
- },
80
- };
81
-
82
- // App has local data that doesn't match server-data. User has to choose
83
- // how to proceed.
84
-
85
- // TODO Style UserMismatch and add handler.
86
- export const UserMismatch: Story = {
87
- args: {
88
- localUser: userB,
89
- },
90
- };
91
-
92
- // TODO: Implement Not Found story (user was probably deleted)
93
- export const UserNotFound: Story = {
94
- args: {
95
- localUser: null,
96
- },
97
- };
98
-
99
- // TODO: Implement no connection story
100
- export const NoConnection: Story = {};
101
-
102
- // TODO: Implement fallback failure (all other cases)
103
- export const FallbackFailure: Story = {
104
- args: {
105
- localUser: userB,
106
- },
107
- };
@@ -1,204 +0,0 @@
1
- import { Form } from "@ariakit/react";
2
- import { Button } from "@ariakit/react/button";
3
-
4
- import { useCallback, useEffect, useState } from "react";
5
- import { Link, useLocation } from "wouter";
6
- import { Pending, type Failure, type Success } from "../async-op.js";
7
- import { IndieTabletopClient } from "../client.ts";
8
- import { interactiveText } from "../common.css.ts";
9
- import { getSubmitFailureMessage } from "../failureMessages.ts";
10
- import { LoadingPage } from "../InfoPage/index.tsx";
11
- import {
12
- Letterhead,
13
- LetterheadHeading,
14
- LetterheadParagraph,
15
- LetterheadSubmitButton,
16
- } from "../Letterhead/index.tsx";
17
- import {
18
- LetterheadSubmitError,
19
- LetterheadTextField,
20
- } from "../LetterheadForm/index.tsx";
21
- import { Title } from "../Title/index.tsx";
22
- import type { CurrentUser, FailurePayload } from "../types.ts";
23
- import { useForm } from "../use-form.ts";
24
- import { validEmail } from "../validations.ts";
25
- import * as css from "./style.css.ts";
26
-
27
- type EventHandler = () => Promise<void> | void;
28
-
29
- function LoginForm(props: {
30
- client: IndieTabletopClient;
31
- onSuccess: EventHandler;
32
- }) {
33
- const { client, onSuccess } = props;
34
-
35
- const [_, navigate] = useLocation();
36
- const { form, submitName } = useForm({
37
- defaultValues: { email: "", password: "" },
38
- validate: { email: validEmail },
39
- async onSubmit({ values }) {
40
- const result = await client.login(values);
41
-
42
- return result.mapFailure((failure) => {
43
- return getSubmitFailureMessage(failure, {
44
- 401: "Username and password do not match. Please try again.",
45
- 404: "Could not find a user with this email.",
46
- });
47
- });
48
- },
49
-
50
- async onSuccess() {
51
- await onSuccess();
52
- navigate("~/");
53
- },
54
- });
55
-
56
- return (
57
- <Form store={form} resetOnSubmit={false}>
58
- <div
59
- style={{
60
- display: "flex",
61
- flexDirection: "column",
62
- gap: "1.125rem",
63
- }}
64
- >
65
- <LetterheadTextField
66
- name={form.names.email}
67
- placeholder="james.workshop@example.com"
68
- label="Email"
69
- type="email"
70
- required
71
- />
72
- <LetterheadTextField
73
- name={form.names.password}
74
- label="Password"
75
- placeholder="Your password"
76
- type="password"
77
- required
78
- />
79
- </div>
80
- <div
81
- style={{
82
- marginBlockStart: "2rem",
83
- display: "flex",
84
- flexDirection: "column",
85
- gap: "1.125rem",
86
- }}
87
- >
88
- <LetterheadSubmitError name={submitName} />
89
- <LetterheadSubmitButton>Log in</LetterheadSubmitButton>
90
- </div>
91
- </Form>
92
- );
93
- }
94
-
95
- function useCurrentUserResult(client: IndieTabletopClient) {
96
- const getCurrentUser = useCallback(() => client.getCurrentUser(), [client]);
97
- const [result, setResult] = useState<
98
- Success<CurrentUser> | Failure<FailurePayload> | Pending
99
- >(new Pending());
100
-
101
- useEffect(() => {
102
- getCurrentUser().then((result) => setResult(result));
103
- }, [getCurrentUser]);
104
-
105
- return result;
106
- }
107
-
108
- export function LoginPage(props: {
109
- client: IndieTabletopClient;
110
- localUser: CurrentUser | null;
111
- onSuccess: EventHandler;
112
- onLogout: EventHandler;
113
- }) {
114
- const { client, localUser, onLogout } = props;
115
- const currentUserResult = useCurrentUserResult(client);
116
-
117
- return (
118
- <div className={css.page}>
119
- <Title>Login</Title>
120
-
121
- {currentUserResult.unpack(
122
- (serverUser) => {
123
- if (localUser && localUser.id !== serverUser.id) {
124
- return (
125
- <Letterhead>
126
- <header className={css.header}>
127
- <LetterheadHeading>User mismatch</LetterheadHeading>
128
-
129
- <LetterheadParagraph>
130
- You are logged into Indie Tabletop Club as{" "}
131
- {serverUser.email}, but the account currently logged in on
132
- this device is {localUser.email}.
133
- </LetterheadParagraph>
134
-
135
- <LetterheadParagraph>
136
- Please pick which user account to continue with to prevent
137
- syncing issues.
138
- </LetterheadParagraph>
139
-
140
- <div>
141
- <Button type="button">
142
- <div>Continue as {serverUser.email}</div>
143
- <div>Local data will be deleted.</div>
144
- </Button>
145
-
146
- <Button type="button">
147
- <div>Continue as {localUser.email}</div>
148
- <div>You will be asked for your credentials again.</div>
149
- </Button>
150
- </div>
151
- </header>
152
- </Letterhead>
153
- );
154
- }
155
-
156
- return (
157
- <Letterhead>
158
- <header className={css.header}>
159
- <LetterheadHeading>Logged in</LetterheadHeading>
160
- <LetterheadParagraph>
161
- You are already logged into Indie Tabletop Club as{" "}
162
- {serverUser.email}.
163
- </LetterheadParagraph>
164
-
165
- <LetterheadParagraph>
166
- <Link className={interactiveText} href="~/">
167
- Continue
168
- </Link>
169
- {` as current user, or `}
170
- <Button className={interactiveText} onClick={onLogout}>
171
- log out
172
- </Button>
173
- .
174
- </LetterheadParagraph>
175
- </header>
176
- </Letterhead>
177
- );
178
- },
179
- (failure) => {
180
- if (failure.type === "API_ERROR" && failure.code === 401) {
181
- return (
182
- <Letterhead>
183
- <header className={css.header}>
184
- <LetterheadHeading>Log in</LetterheadHeading>
185
- <LetterheadParagraph>
186
- Log into your Indie Tabletop Club account to access creator
187
- features.
188
- </LetterheadParagraph>
189
- </header>
190
-
191
- <LoginForm {...props} />
192
- </Letterhead>
193
- );
194
- }
195
-
196
- return null;
197
- },
198
- () => (
199
- <LoadingPage />
200
- ),
201
- )}
202
- </div>
203
- );
204
- }
@@ -1,17 +0,0 @@
1
- import { MinWidth } from "@indietabletop/appkit";
2
- import { style } from "@vanilla-extract/css";
3
-
4
- export const page = style({
5
- backgroundColor: "white",
6
-
7
- "@media": {
8
- [MinWidth.SMALL]: {
9
- backgroundColor: "transparent",
10
- padding: "clamp(1rem, 5vw, 5rem)",
11
- },
12
- },
13
- });
14
-
15
- export const header = style({
16
- marginBlockEnd: "3rem",
17
- });