@indietabletop/appkit 5.2.1 → 5.3.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.
@@ -0,0 +1,50 @@
1
+ type InputValues<K extends string | number, V> = Record<K, V> | Array<[K, V]>;
2
+
3
+ /**
4
+ * Handles mapping enums to arbitrary values.
5
+ */
6
+ export class EnumMapper<K extends string | number, V> {
7
+ private map: Map<K, V>;
8
+ private fallback: V;
9
+
10
+ constructor(values: InputValues<K, V>, fallback: V) {
11
+ const entries = Array.isArray(values)
12
+ ? values
13
+ : (Object.entries(values) as Array<[K, V]>);
14
+ this.map = new Map(entries);
15
+ this.fallback = fallback;
16
+ }
17
+
18
+ /**
19
+ * Returns a value corresponding to the provided key.
20
+ *
21
+ * If no match is found, returns the fallback value provided in the constructor.
22
+ *
23
+ * Note that TypeScript will make sure that we only ever use the right types
24
+ * as the enum param, but it can still happen at runtime that different
25
+ * values are provided.
26
+ */
27
+ get(key: K | undefined): V {
28
+ if (key === undefined) {
29
+ return this.fallback;
30
+ }
31
+
32
+ const value = this.map.get(key);
33
+ if (value === undefined) {
34
+ return this.fallback;
35
+ }
36
+
37
+ return value;
38
+ }
39
+
40
+ /**
41
+ * All enums known by this mapper.
42
+ */
43
+ get enums() {
44
+ return Array.from(this.map.keys());
45
+ }
46
+
47
+ static from<K extends string>(values: InputValues<K, string>) {
48
+ return new EnumMapper(values, "Unknown");
49
+ }
50
+ }
package/lib/client.ts CHANGED
@@ -1,8 +1,23 @@
1
- import type { SpaceGits } from "@indietabletop/types";
2
- import { type Infer, mask, object, string, Struct, unknown } from "superstruct";
3
- import { Failure, Success } from "./async-op.js";
4
- import { currentUser, redeemedPledge, sessionInfo } from "./structs.js";
5
- import type { CurrentUser, FailurePayload, SessionInfo } from "./types.js";
1
+ import {
2
+ currentUser,
3
+ featureUnlock,
4
+ ownedProduct,
5
+ redeemedPledge,
6
+ sessionInfo,
7
+ type FeatureUnlock,
8
+ type SpaceGits,
9
+ } from "@indietabletop/types";
10
+ import {
11
+ array,
12
+ mask,
13
+ object,
14
+ string,
15
+ Struct,
16
+ unknown,
17
+ type Infer,
18
+ } from "superstruct";
19
+ import { Failure, Success } from "./async-op.ts";
20
+ import type { CurrentUser, FailurePayload, SessionInfo } from "./types.ts";
6
21
 
7
22
  export type UserGameData = {
8
23
  spacegits?: {
@@ -571,4 +586,47 @@ export class IndieTabletopClient {
571
586
  },
572
587
  );
573
588
  }
589
+
590
+ async getUnlocks<T extends FeatureUnlock["gameCode"]>(gameCode: T) {
591
+ return this.fetch(
592
+ `/v1/me/unlocks/${gameCode}`,
593
+ object({
594
+ unlocks: array(
595
+ featureUnlock() as unknown as Struct<
596
+ Extract<FeatureUnlock, { gameCode: T }>,
597
+ null
598
+ >,
599
+ ),
600
+ }),
601
+ );
602
+ }
603
+
604
+ async getOwnedProduct(productCode: string) {
605
+ return await this.fetch(`/v1/me/products/${productCode}`, ownedProduct());
606
+ }
607
+
608
+ async startCheckoutSession(payload: {
609
+ products: { code: string; quantity: number }[];
610
+ successUrl: string;
611
+ cancelUrl: string;
612
+ }) {
613
+ return await this.fetch(
614
+ `/v1/stripe/sessions`,
615
+ object({ redirectTo: string() }),
616
+ {
617
+ method: "POST",
618
+ json: payload,
619
+ },
620
+ );
621
+ }
622
+
623
+ async getCheckoutSession(sessionId: string) {
624
+ return await this.fetch(
625
+ `/v1/stripe/sessions/${sessionId}`,
626
+ object({
627
+ status: string(),
628
+ products: array(ownedProduct()),
629
+ }),
630
+ );
631
+ }
574
632
  }
package/lib/index.ts CHANGED
@@ -35,6 +35,7 @@ export * from "./use-media-query.ts";
35
35
  export * from "./use-reverting-state.ts";
36
36
  export * from "./use-scroll-restoration.ts";
37
37
  export * from "./useEnsureValue.ts";
38
+ export * from "./useInvokeClient.ts";
38
39
  export * from "./useIsVisible.ts";
39
40
 
40
41
  // Utils
@@ -64,5 +65,6 @@ export * from "./utm.ts";
64
65
  export * from "./validations.ts";
65
66
 
66
67
  // Other
68
+ export * from "./EnumMapper.ts";
67
69
  export * from "./ModernIDB/index.ts";
68
70
  export * from "./store/index.tsx";
package/lib/structs.ts CHANGED
@@ -1,27 +1,3 @@
1
- import { boolean, enums, number, object, optional, string } from "superstruct";
2
-
3
- export function currentUser() {
4
- return object({
5
- id: string(),
6
- email: string(),
7
- isVerified: boolean(),
8
- prefersScrollbarVisibility: optional(enums(["ALWAYS"])),
9
- });
10
- }
11
-
12
- export function sessionInfo() {
13
- return object({
14
- expiresTs: number(),
15
- createdTs: number(),
16
- });
17
- }
18
-
19
- export function redeemedPledge() {
20
- return object({
21
- id: string(),
22
- downloadUrl: string(),
23
- downloadUrlExpiresTs: number(),
24
- contactSubscribed: boolean(),
25
- email: string(),
26
- });
27
- }
1
+ // Structs are exported from here for backwards compat. Generally, types
2
+ // should be used directly from the types package.
3
+ export { currentUser, redeemedPledge, sessionInfo } from "@indietabletop/types";
@@ -0,0 +1,54 @@
1
+ import { useCallback } from "react";
2
+ import type { SWRConfiguration, SWRResponse } from "swr";
3
+ import useSWR from "swr";
4
+ import type { Pending } from "./async-op.ts";
5
+ import type { IndieTabletopClient } from "./client.ts";
6
+ import { swrResponseToResult } from "./result/swr.ts";
7
+
8
+ /**
9
+ * Creates a `useInvokeClient` hook that makes it ergonomic to invoke
10
+ * client methods.
11
+ *
12
+ * Note that only methods starting with `get` are picked up. This hook is
13
+ * intended for making easy fetches, not for form submissions.
14
+ */
15
+ export function createUseInvokeClient<C extends IndieTabletopClient>(c: C) {
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ type Fn = (...args: any) => any;
18
+
19
+ return function useInvokeClient<K extends keyof C & `get${string}`>(
20
+ /**
21
+ * ITC Client's method name.
22
+ */
23
+ method: K,
24
+
25
+ /**
26
+ * Method's arguments.
27
+ *
28
+ * **IMPORTANT**: Must be an array or primitive values as it is directly
29
+ * used as a `useCallback` dependecy array.
30
+ */
31
+ args: C[K] extends Fn ? Parameters<C[K]> : never,
32
+
33
+ options?: {
34
+ performFetch?: boolean;
35
+ swr?: SWRConfiguration;
36
+ },
37
+ ): C[K] extends Fn
38
+ ? [Awaited<ReturnType<C[K]>> | Pending, SWRResponse<unknown, unknown>]
39
+ : never {
40
+ const { performFetch = true } = options ?? {};
41
+
42
+ const callback = useCallback(() => {
43
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
44
+ return (c[method] as Fn)(...args);
45
+
46
+ // eslint-disable-next-line react-hooks/exhaustive-deps
47
+ }, [method, ...args]);
48
+
49
+ const cacheKey = [method, ...args].join(":");
50
+ const swr = useSWR(performFetch ? cacheKey : null, callback, options?.swr);
51
+
52
+ return [swrResponseToResult(swr), swr] as never;
53
+ };
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indietabletop/appkit",
3
- "version": "5.2.1",
3
+ "version": "5.3.0",
4
4
  "description": "A collection of modules used in apps built by Indie Tabletop Club",
5
5
  "private": false,
6
6
  "type": "module",
@@ -44,7 +44,7 @@
44
44
  "dependencies": {
45
45
  "@ariakit/react": "^0.4.17",
46
46
  "@indietabletop/tooling": "^5.0.0",
47
- "@indietabletop/types": "^1.0.0-3",
47
+ "@indietabletop/types": "^1.1.0",
48
48
  "@vanilla-extract/css": "^1.17.2",
49
49
  "@vanilla-extract/dynamic": "^2.1.3",
50
50
  "@vanilla-extract/recipes": "^0.5.7",