@indietabletop/appkit 7.0.0-rc.1 → 7.0.0-rc.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,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, useSearchParams } 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";
@@ -38,6 +38,7 @@ type InitialStepProps = {
38
38
  };
39
39
 
40
40
  function InitialStep(props: InitialStepProps) {
41
+ const [params] = useSearchParams();
41
42
  const { client, placeholders, hrefs } = useAppConfig();
42
43
  const { defaultValues, setStep } = props;
43
44
 
@@ -142,7 +143,10 @@ function InitialStep(props: InitialStepProps) {
142
143
  {"Have an existing account? "}
143
144
  <Link
144
145
  {...cx(interactiveText)}
145
- href={hrefs.login()}
146
+ href={hrefs.login({
147
+ redirectTo: params.get("redirectTo") ?? undefined,
148
+ backTo: params.get("backTo") ?? undefined,
149
+ })}
146
150
  state={{ emailValue }}
147
151
  >
148
152
  Log in
@@ -1,6 +1,6 @@
1
1
  import { Button, Form, useStoreState } from "@ariakit/react";
2
2
  import type { ReactNode } from "react";
3
- import { Link, useLocation } from "wouter";
3
+ import { Link, useLocation, useSearchParams } 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";
@@ -31,6 +31,7 @@ export function LoginView(props: {
31
31
  description: ReactNode;
32
32
  reload: () => void;
33
33
  }) {
34
+ const [params] = useSearchParams();
34
35
  const { currentUser, defaultValues, description, onLogin, reload } = props;
35
36
  const { placeholders, client, hrefs } = useAppConfig();
36
37
  const { logout } = useAppActions();
@@ -73,7 +74,7 @@ export function LoginView(props: {
73
74
  <LetterheadHeader>
74
75
  <LetterheadHeading>Log in</LetterheadHeading>
75
76
 
76
- {localUserPresent ? (
77
+ {localUserPresent ?
77
78
  <>
78
79
  <LetterheadParagraph>
79
80
  Your session has expired. Please log into Indie Tabletop Club
@@ -94,12 +95,14 @@ export function LoginView(props: {
94
95
  {" first."}
95
96
  </LetterheadParagraph>
96
97
  </>
97
- ) : (
98
- <LetterheadParagraph>
98
+ : <LetterheadParagraph>
99
99
  {description}
100
100
  {" Do not have an account? "}
101
101
  <Link
102
- href={hrefs.join()}
102
+ href={hrefs.join({
103
+ redirectTo: params.get("redirectTo") ?? undefined,
104
+ backTo: params.get("backTo") ?? undefined,
105
+ })}
103
106
  className={interactiveText}
104
107
  state={{ emailValue }}
105
108
  >
@@ -107,7 +110,7 @@ export function LoginView(props: {
107
110
  </Link>
108
111
  {"."}
109
112
  </LetterheadParagraph>
110
- )}
113
+ }
111
114
  </LetterheadHeader>
112
115
 
113
116
  <Form store={form} resetOnSubmit={false}>
package/lib/fathom.ts ADDED
@@ -0,0 +1,15 @@
1
+ declare global {
2
+ interface Window {
3
+ fathom?: { trackEvent: (name: string) => void };
4
+ }
5
+ }
6
+
7
+ export function trackEvent(event: string) {
8
+ if (window.fathom) {
9
+ window.fathom.trackEvent(event);
10
+ } else {
11
+ console.warn(
12
+ `Attempting to track event ${event}, but Fathom not installed.`,
13
+ );
14
+ }
15
+ }
package/lib/hrefs.ts CHANGED
@@ -1,10 +1,28 @@
1
1
  import type { LinkUtmParams, createUtm } from "./utm.ts";
2
2
 
3
+ export function withParams(path: string, params?: Record<string, string>) {
4
+ if (!params) {
5
+ return path;
6
+ }
7
+
8
+ return `${path}?${new URLSearchParams(params)}`;
9
+ }
10
+
11
+ export type AccountParams = {
12
+ redirectTo?: string;
13
+ backTo?: string;
14
+ };
15
+
16
+ export type BuyParams = {
17
+ note?: "continuePurchase" | "loginNeeded";
18
+ };
19
+
3
20
  type InputAppHrefs = {
4
- login: () => string;
5
- password: () => string;
6
- join: () => string;
7
- dashboard: () => string;
21
+ login?: (params?: AccountParams) => string;
22
+ join?: (params?: AccountParams) => string;
23
+ password?: () => string;
24
+
25
+ dashboard?: () => string;
8
26
  account: () => string;
9
27
 
10
28
  // These are usually external links to the root domain, so we want to be
@@ -33,6 +51,12 @@ export function createHrefs<T extends InputAppHrefs>(params: {
33
51
  const { utm, hrefs } = params;
34
52
 
35
53
  return {
54
+ dashboard: () => `~/`,
55
+
56
+ login: (params?: AccountParams) => withParams(`~/login`, params),
57
+ join: (params?: AccountParams) => withParams(`~/join`, params),
58
+ password: () => `~/password`,
59
+
36
60
  terms: (linkUtm?: LinkUtmParams) =>
37
61
  `https://indietabletop.club/terms?${utm(linkUtm)}`,
38
62
  privacy: (linkUtm?: LinkUtmParams) =>
package/lib/index.ts CHANGED
@@ -43,10 +43,12 @@ export * from "./use-reverting-state.ts";
43
43
  export * from "./use-scroll-restoration.ts";
44
44
  export * from "./useEnsureValue.ts";
45
45
  export * from "./useFetchJson.tsx";
46
+ export * from "./useGetProductStatus.ts";
46
47
  export * from "./useInvokeClient.ts";
47
48
  export * from "./useIsVisible.ts";
48
49
 
49
50
  // Utils
51
+
50
52
  export * from "./append-copy-to-text.ts";
51
53
  export * from "./async-op.ts";
52
54
  export * from "./caught-value.ts";
@@ -55,6 +57,7 @@ export * from "./client.ts";
55
57
  export * from "./copyrightRange.ts";
56
58
  export * from "./createSafeStorage.ts";
57
59
  export * from "./failureMessages.ts";
60
+ export * from "./fathom.ts";
58
61
  export * from "./groupBy.ts";
59
62
  export * from "./HistoryState.ts";
60
63
  export * from "./hrefs.ts";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Given an object with keys that might contain undefined values, returns a new
3
+ * object only with keys that are not undefined.
4
+ */
5
+ export function omitUndefinedKeys<T>(record: Record<string, T | undefined>) {
6
+ return Object.fromEntries(
7
+ Object.entries(record).filter(([_, v]) => v !== undefined),
8
+ ) as Record<string, T>;
9
+ }
@@ -0,0 +1,56 @@
1
+ import { useCallback } from "react";
2
+ import useSWR from "swr";
3
+ import { useClient } from "./AppConfig/AppConfig.tsx";
4
+ import { Failure, Success } from "./async-op.ts";
5
+ import { swrResponseToResult } from "./result/swr.ts";
6
+ import { useCurrentUser } from "./store/index.tsx";
7
+
8
+ function useGetOwnedProduct(productCode: string) {
9
+ const client = useClient();
10
+
11
+ const swr = useSWR(
12
+ `getOwnedProduct:${productCode}`,
13
+ useCallback(() => {
14
+ return client.getOwnedProduct(productCode);
15
+ }, [productCode]),
16
+ );
17
+
18
+ return swrResponseToResult(swr);
19
+ }
20
+
21
+ export function useGetProductStatus(productCode: string) {
22
+ const result = useGetOwnedProduct(productCode);
23
+ const currentUser = useCurrentUser();
24
+
25
+ if (result.isPending) {
26
+ return result;
27
+ }
28
+
29
+ if (result.isFailure) {
30
+ const { failure } = result;
31
+
32
+ if (failure.type === "API_ERROR") {
33
+ // The no currentUser case is possible in case the user is logged into ITC
34
+ // via a domain-wide cookie, but they have not yet logged in this app.
35
+ if (failure.code === 401 || !currentUser) {
36
+ return new Success({ type: "AUTHENTICATE" as const });
37
+ }
38
+
39
+ if (failure.code === 403) {
40
+ return new Success({ type: "PURCHASE" as const });
41
+ }
42
+ }
43
+
44
+ // Other errors should be handled by the default error handling
45
+ return result;
46
+ }
47
+
48
+ // This shouldn't happen in practice, as this particular endpoint errors out
49
+ // if a product link fails to generate.
50
+ const { downloadUrl } = result.value;
51
+ if (!downloadUrl) {
52
+ return new Failure({ type: "UNKNOWN_ERROR" as const });
53
+ }
54
+
55
+ return new Success({ type: "DOWNLOAD" as const, downloadUrl });
56
+ }
package/lib/utm.ts CHANGED
@@ -1,12 +1,4 @@
1
- /**
2
- * Given an object with keys that might contain undefined values, returns a new
3
- * object only with keys that are not undefined.
4
- */
5
- function omitUndefinedKeys<T>(record: Record<string, T | undefined>) {
6
- return Object.fromEntries(
7
- Object.entries(record).filter(([_, v]) => v !== undefined),
8
- ) as Record<string, T>;
9
- }
1
+ import { omitUndefinedKeys } from "./omitUndefinedKeys.ts";
10
2
 
11
3
  /**
12
4
  * Full UTM configuration object.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indietabletop/appkit",
3
- "version": "7.0.0-rc.1",
3
+ "version": "7.0.0-rc.2",
4
4
  "description": "A collection of modules used in apps built by Indie Tabletop Club",
5
5
  "private": false,
6
6
  "type": "module",