@indietabletop/appkit 5.0.0 → 5.1.0
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/lib/account/JoinCard.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Form, useStoreState } from "@ariakit/react";
|
|
2
2
|
import { type Dispatch, type SetStateAction, useState } from "react";
|
|
3
|
-
import { Link } from "wouter";
|
|
3
|
+
import { Link, useLocation } from "wouter";
|
|
4
4
|
import { useAppConfig, useClient } from "../AppConfig/AppConfig.tsx";
|
|
5
5
|
import { cx } from "../class-names.ts";
|
|
6
6
|
import { interactiveText } from "../common.css.ts";
|
|
@@ -28,13 +28,16 @@ import { LoadingView } from "./LoadingView.tsx";
|
|
|
28
28
|
import { NoConnectionView } from "./NoConnectionView.tsx";
|
|
29
29
|
import type { DefaultFormValues } from "./types.ts";
|
|
30
30
|
import { useFetchCurrentUser } from "./useFetchCurrentUser.tsx";
|
|
31
|
+
import { useRedirectPath } from "./useRedirectPath.ts";
|
|
31
32
|
|
|
32
33
|
type SetStep = Dispatch<SetStateAction<JoinStep>>;
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
type InitialStepProps = {
|
|
35
36
|
setStep: SetStep;
|
|
36
37
|
defaultValues?: DefaultFormValues;
|
|
37
|
-
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function InitialStep(props: InitialStepProps) {
|
|
38
41
|
const { client, placeholders, hrefs } = useAppConfig();
|
|
39
42
|
const { defaultValues, setStep } = props;
|
|
40
43
|
|
|
@@ -152,13 +155,22 @@ function InitialStep(props: {
|
|
|
152
155
|
);
|
|
153
156
|
}
|
|
154
157
|
|
|
155
|
-
|
|
158
|
+
type SubmitCodeStepProps = {
|
|
156
159
|
tokenId: string;
|
|
157
160
|
setStep: SetStep;
|
|
158
161
|
email: string;
|
|
159
|
-
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
function SubmitCodeStep(props: SubmitCodeStepProps) {
|
|
160
165
|
const { tokenId, email, setStep } = props;
|
|
166
|
+
const [_, navigate] = useLocation();
|
|
161
167
|
const client = useClient();
|
|
168
|
+
const redirectPath = useRedirectPath();
|
|
169
|
+
|
|
170
|
+
// Use redirect path if found in search params, otherwise go the success step.
|
|
171
|
+
const onSuccess = redirectPath
|
|
172
|
+
? () => navigate(redirectPath)
|
|
173
|
+
: () => setStep({ type: "SUCCESS" });
|
|
162
174
|
|
|
163
175
|
const { form, submitName } = useForm({
|
|
164
176
|
defaultValues: {
|
|
@@ -173,9 +185,7 @@ function SubmitCodeStep(props: {
|
|
|
173
185
|
});
|
|
174
186
|
});
|
|
175
187
|
},
|
|
176
|
-
onSuccess
|
|
177
|
-
setStep({ type: "SUCCESS" });
|
|
178
|
-
},
|
|
188
|
+
onSuccess,
|
|
179
189
|
});
|
|
180
190
|
|
|
181
191
|
return (
|
|
@@ -229,7 +239,11 @@ type JoinStep =
|
|
|
229
239
|
| { type: "SUBMIT_CODE"; tokenId: string; email: string }
|
|
230
240
|
| { type: "SUCCESS" };
|
|
231
241
|
|
|
232
|
-
|
|
242
|
+
type JoinFlowProps = {
|
|
243
|
+
defaultValues?: DefaultFormValues;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
function JoinFlow(props: JoinFlowProps) {
|
|
233
247
|
const [step, setStep] = useState<JoinStep>({ type: "INITIAL" });
|
|
234
248
|
|
|
235
249
|
switch (step.type) {
|
|
@@ -253,6 +267,12 @@ export type JoinCardProps = {
|
|
|
253
267
|
|
|
254
268
|
/**
|
|
255
269
|
* Allows the user to join Indie Tabletop Club.
|
|
270
|
+
*
|
|
271
|
+
* Will automatically use the `redirectTo` query param value as the redirect
|
|
272
|
+
* location once the user creates and verifies their account.
|
|
273
|
+
*
|
|
274
|
+
* Otherwise a success screen will be shown and the user will be directed to
|
|
275
|
+
* the dashboard.
|
|
256
276
|
*/
|
|
257
277
|
export function JoinCard(props: JoinCardProps) {
|
|
258
278
|
const { result, latestAttemptTs, reload } = useFetchCurrentUser();
|
|
@@ -33,14 +33,21 @@ export type LoginCardProps = {
|
|
|
33
33
|
/**
|
|
34
34
|
* Called when the login action succeeds.
|
|
35
35
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* If no handler is provided, the component will will navigate to the
|
|
37
|
+
* dashboard (as determined by `appConfig.hrefs.dashboard()` return value),
|
|
38
|
+
* or a location provided in `redirectTo` query param if it is a valid
|
|
39
|
+
* local path.
|
|
38
40
|
*/
|
|
39
|
-
onLogin
|
|
41
|
+
onLogin?: AuthEventHandler;
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
/**
|
|
43
45
|
* Allows the user to log into Indie Tabletop Club.
|
|
46
|
+
*
|
|
47
|
+
* Will automatically use the `redirectTo` query param value as the redirect
|
|
48
|
+
* location after successful login.
|
|
49
|
+
*
|
|
50
|
+
* Otherwise, the user will be redirected to the app's dashboard.
|
|
44
51
|
*/
|
|
45
52
|
export function LoginCard(props: LoginCardProps) {
|
|
46
53
|
const currentUser = useCurrentUser();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Button, Form, useStoreState } from "@ariakit/react";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
-
import { Link } from "wouter";
|
|
3
|
+
import { Link, useLocation } from "wouter";
|
|
4
4
|
import { useAppConfig } from "../AppConfig/AppConfig.tsx";
|
|
5
5
|
import { interactiveText } from "../common.css.ts";
|
|
6
6
|
import { getSubmitFailureMessage } from "../failureMessages.ts";
|
|
@@ -22,10 +22,11 @@ import type { CurrentUser } from "../types.ts";
|
|
|
22
22
|
import { useForm } from "../use-form.ts";
|
|
23
23
|
import { validEmail } from "../validations.ts";
|
|
24
24
|
import type { AuthEventHandler, DefaultFormValues } from "./types.ts";
|
|
25
|
+
import { useRedirectPath } from "./useRedirectPath.ts";
|
|
25
26
|
|
|
26
27
|
export function LoginView(props: {
|
|
27
28
|
defaultValues?: DefaultFormValues;
|
|
28
|
-
onLogin
|
|
29
|
+
onLogin?: AuthEventHandler;
|
|
29
30
|
currentUser: CurrentUser | null;
|
|
30
31
|
description: ReactNode;
|
|
31
32
|
reload: () => void;
|
|
@@ -33,8 +34,11 @@ export function LoginView(props: {
|
|
|
33
34
|
const { currentUser, defaultValues, description, onLogin, reload } = props;
|
|
34
35
|
const { placeholders, client, hrefs } = useAppConfig();
|
|
35
36
|
const { logout } = useAppActions();
|
|
37
|
+
|
|
36
38
|
const localUserPresent = !!currentUser?.email;
|
|
37
39
|
const defaultEmailValue = currentUser?.email ?? defaultValues?.email ?? "";
|
|
40
|
+
const redirectPath = useRedirectPath() ?? hrefs.dashboard();
|
|
41
|
+
const [_, navigate] = useLocation();
|
|
38
42
|
|
|
39
43
|
const { form, submitName } = useForm({
|
|
40
44
|
defaultValues: { email: defaultEmailValue, password: "" },
|
|
@@ -50,7 +54,15 @@ export function LoginView(props: {
|
|
|
50
54
|
});
|
|
51
55
|
},
|
|
52
56
|
onSuccess() {
|
|
53
|
-
onLogin
|
|
57
|
+
if (onLogin) {
|
|
58
|
+
onLogin();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// If a custom onLogin handler is not provided, will automatically
|
|
62
|
+
// navigate to the appropriate redirect path.
|
|
63
|
+
else {
|
|
64
|
+
navigate(redirectPath);
|
|
65
|
+
}
|
|
54
66
|
},
|
|
55
67
|
});
|
|
56
68
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useSearchParams } from "wouter";
|
|
2
|
+
|
|
3
|
+
function isValidRedirect(value: string | null | undefined): value is string {
|
|
4
|
+
return !!(value && /^[~/]/.test(value));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Gets redirect path from query params and validates it.
|
|
9
|
+
*
|
|
10
|
+
* Returns `null` if no path is found, or it is invalid.
|
|
11
|
+
*/
|
|
12
|
+
export function useRedirectPath() {
|
|
13
|
+
const [params] = useSearchParams();
|
|
14
|
+
const redirectTo = params.get("redirectTo");
|
|
15
|
+
|
|
16
|
+
if (isValidRedirect(redirectTo)) {
|
|
17
|
+
return redirectTo;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return null;
|
|
21
|
+
}
|