@withone/connect 0.10.0 → 0.12.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/src/vue.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * <ConnectButton
5
5
  * authorize-url="/api/one/authorize"
6
6
  * :platforms="['stripe', 'notion']"
7
+ * :connected="user.hasOneGrant"
7
8
  * @success="onConnected"
8
9
  * />
9
10
  *
@@ -21,59 +22,74 @@ import type { PropType } from "vue";
21
22
 
22
23
  import {
23
24
  mountConnectButton,
24
- optionsFromProps,
25
- propsIdentity,
26
25
  type ConnectButtonHandle,
27
26
  type ConnectButtonPlatformInput,
27
+ type ConnectButtonProps,
28
+ type ConnectButtonSize,
29
+ type ConnectButtonTheme,
28
30
  type ConnectButtonVariant,
31
+ type ConnectFailureCode,
29
32
  type OneConnectTheme,
30
33
  } from "@withone/connect";
31
34
 
35
+ /** Booleans default to undefined, not false, so "not set" stays
36
+ * distinguishable from "false" (it matters for `connected`). */
37
+ const optionalBoolean = { type: Boolean, default: undefined };
38
+
32
39
  export const ConnectButton = defineComponent({
33
40
  name: "OneConnectButton",
34
41
  props: {
35
42
  authorizeUrl: { type: String, required: true },
36
- appTheme: { type: String as PropType<OneConnectTheme>, default: undefined },
37
- label: { type: String, default: undefined },
43
+ platforms: {
44
+ type: Array as PropType<ConnectButtonPlatformInput[]>,
45
+ default: undefined,
46
+ },
47
+ connected: optionalBoolean,
48
+ disabled: optionalBoolean,
38
49
  variant: {
39
50
  type: String as PropType<ConnectButtonVariant>,
40
51
  default: undefined,
41
52
  },
42
- theme: { type: String as PropType<OneConnectTheme>, default: undefined },
43
- platforms: {
44
- type: Array as PropType<ConnectButtonPlatformInput[]>,
53
+ size: { type: String as PropType<ConnectButtonSize>, default: undefined },
54
+ fullWidth: optionalBoolean,
55
+ theme: { type: String as PropType<ConnectButtonTheme>, default: undefined },
56
+ connectTheme: {
57
+ type: String as PropType<OneConnectTheme>,
45
58
  default: undefined,
46
59
  },
47
- description: { type: String, default: undefined },
60
+ /** @deprecated use connectTheme */
61
+ appTheme: { type: String as PropType<OneConnectTheme>, default: undefined },
48
62
  accentColor: { type: String, default: undefined },
63
+ label: { type: String, default: undefined },
49
64
  connectedLabel: { type: String, default: undefined },
65
+ description: { type: String, default: undefined },
50
66
  },
51
67
  emits: {
52
68
  success: () => true,
53
- error: (message: string) => typeof message === "string",
69
+ error: (message: string, code: ConnectFailureCode) =>
70
+ typeof message === "string" && typeof code === "string",
71
+ cancel: () => true,
54
72
  },
55
73
  setup(props, { emit }) {
56
74
  const container = ref<HTMLElement | null>(null);
57
75
  let handle: ConnectButtonHandle | null = null;
58
76
 
59
- const mount = () => {
60
- handle?.destroy();
61
- handle = null;
62
- if (!container.value) return;
63
- handle = mountConnectButton(
64
- container.value,
65
- optionsFromProps(
66
- { ...props },
67
- {
68
- onSuccess: () => emit("success"),
69
- onError: (message) => emit("error", message),
70
- },
71
- ),
72
- );
73
- };
77
+ const current = (): ConnectButtonProps => ({
78
+ ...props,
79
+ onSuccess: () => emit("success"),
80
+ onError: (message, code) => emit("error", message, code),
81
+ onCancel: () => emit("cancel"),
82
+ });
74
83
 
75
- onMounted(mount);
76
- watch(() => propsIdentity({ ...props }), mount);
84
+ onMounted(() => {
85
+ if (container.value)
86
+ handle = mountConnectButton(container.value, current());
87
+ });
88
+ watch(
89
+ () => ({ ...props }),
90
+ () => handle?.update(current()),
91
+ { deep: true },
92
+ );
77
93
  onBeforeUnmount(() => {
78
94
  handle?.destroy();
79
95
  handle = null;
@@ -1,13 +0,0 @@
1
- import type { OneConnectHandle, OneConnectOptions } from "./types";
2
- /**
3
- * Wires the connect flow to any element. A plain function rather than a
4
- * React hook, so it works from every framework: call it once where the
5
- * button lives and pass `open` to a click.
6
- *
7
- * The flow is a full-page redirect: the tab goes to the app's authorize
8
- * route, on to One's hosted page, back to the app's callback route, and
9
- * home. On the way home the callback route appends `?one_connect=…` to
10
- * the URL it redirects to; this function reads that on load, fires
11
- * onSuccess or onError once, and removes the params from the address bar.
12
- */
13
- export declare const useOneConnect: (options: OneConnectOptions) => OneConnectHandle;
@@ -1,28 +0,0 @@
1
- import type { ConnectButtonOptions, ConnectButtonPlatformInput, ConnectButtonVariant, OneConnectTheme } from "./types";
2
- /** The flat prop shape every framework wrapper exposes: React props,
3
- * Vue props, the Svelte action's options. One place turns it into the
4
- * core mountConnectButton options. */
5
- export interface ConnectButtonProps {
6
- /** The app's own backend authorize route; relative is fine. */
7
- authorizeUrl: string;
8
- /** Theme of One's hosted page. */
9
- appTheme?: OneConnectTheme;
10
- onSuccess?: () => void;
11
- onError?: (message: string) => void;
12
- label?: string;
13
- variant?: ConnectButtonVariant;
14
- /** Matches the host page. */
15
- theme?: OneConnectTheme;
16
- /** Connector slugs, or objects to override name or logo. */
17
- platforms?: ConnectButtonPlatformInput[];
18
- description?: string;
19
- accentColor?: string;
20
- connectedLabel?: string;
21
- }
22
- export interface ConnectButtonCallbacks {
23
- onSuccess?: () => void;
24
- onError?: (message: string) => void;
25
- }
26
- export declare function optionsFromProps(props: ConnectButtonProps, callbacks?: ConnectButtonCallbacks): ConnectButtonOptions;
27
- /** The inputs whose change should remount the button. */
28
- export declare function propsIdentity(props: ConnectButtonProps): string;
@@ -1,72 +0,0 @@
1
- import { THEME_PARAM } from "./constants";
2
- import { hasReturnParams, parseReturn, stripReturnParams } from "./return";
3
- import type { OneConnectHandle, OneConnectOptions } from "./types";
4
-
5
- /** The return leg is a page load, so it is delivered exactly once, to
6
- * the first instance that reads it. */
7
- let returnConsumed = false;
8
-
9
- const DEFAULT_ERROR = "The connection was not completed.";
10
-
11
- /**
12
- * Wires the connect flow to any element. A plain function rather than a
13
- * React hook, so it works from every framework: call it once where the
14
- * button lives and pass `open` to a click.
15
- *
16
- * The flow is a full-page redirect: the tab goes to the app's authorize
17
- * route, on to One's hosted page, back to the app's callback route, and
18
- * home. On the way home the callback route appends `?one_connect=…` to
19
- * the URL it redirects to; this function reads that on load, fires
20
- * onSuccess or onError once, and removes the params from the address bar.
21
- */
22
- export const useOneConnect = (options: OneConnectOptions): OneConnectHandle => {
23
- const buildAuthorizeUrl = (): string => {
24
- try {
25
- const url = new URL(
26
- options.authorizeUrl,
27
- typeof window === "undefined" ? undefined : window.location.origin,
28
- );
29
- if (options.appTheme) url.hash = `${THEME_PARAM}=${options.appTheme}`;
30
- return url.toString();
31
- } catch {
32
- return options.authorizeUrl;
33
- }
34
- };
35
-
36
- const open = () => {
37
- if (typeof window === "undefined") return;
38
- window.location.assign(buildAuthorizeUrl());
39
- };
40
-
41
- if (typeof window !== "undefined" && !returnConsumed) {
42
- const outcome = parseReturn(window.location.search);
43
- if (outcome) {
44
- returnConsumed = true;
45
- // Frameworks that manage the history themselves (the Next.js App
46
- // Router) sync the address bar back to their own URL when hydration
47
- // completes, undoing a single replaceState. Scrub now and re-check a
48
- // few beats later.
49
- const scrub = () => {
50
- if (!hasReturnParams(window.location.search)) return;
51
- window.history.replaceState(
52
- null,
53
- "",
54
- stripReturnParams(window.location),
55
- );
56
- };
57
- scrub();
58
- for (const delay of [50, 500, 2000]) window.setTimeout(scrub, delay);
59
-
60
- window.setTimeout(() => {
61
- try {
62
- if (outcome.status === "success") options.onSuccess?.();
63
- else options.onError?.(outcome.message ?? DEFAULT_ERROR);
64
- } catch {
65
- /* the app's own callback threw; not ours to handle */
66
- }
67
- }, 0);
68
- }
69
- }
70
-
71
- return { open };
72
- };
@@ -1,68 +0,0 @@
1
- import type {
2
- ConnectButtonOptions,
3
- ConnectButtonPlatformInput,
4
- ConnectButtonVariant,
5
- OneConnectTheme,
6
- } from "./types";
7
-
8
- /** The flat prop shape every framework wrapper exposes: React props,
9
- * Vue props, the Svelte action's options. One place turns it into the
10
- * core mountConnectButton options. */
11
- export interface ConnectButtonProps {
12
- /** The app's own backend authorize route; relative is fine. */
13
- authorizeUrl: string;
14
- /** Theme of One's hosted page. */
15
- appTheme?: OneConnectTheme;
16
- onSuccess?: () => void;
17
- onError?: (message: string) => void;
18
- label?: string;
19
- variant?: ConnectButtonVariant;
20
- /** Matches the host page. */
21
- theme?: OneConnectTheme;
22
- /** Connector slugs, or objects to override name or logo. */
23
- platforms?: ConnectButtonPlatformInput[];
24
- description?: string;
25
- accentColor?: string;
26
- connectedLabel?: string;
27
- }
28
-
29
- export interface ConnectButtonCallbacks {
30
- onSuccess?: () => void;
31
- onError?: (message: string) => void;
32
- }
33
-
34
- export function optionsFromProps(
35
- props: ConnectButtonProps,
36
- callbacks: ConnectButtonCallbacks = props,
37
- ): ConnectButtonOptions {
38
- return {
39
- connect: {
40
- authorizeUrl: props.authorizeUrl,
41
- appTheme: props.appTheme,
42
- onSuccess: () => callbacks.onSuccess?.(),
43
- onError: (message) => callbacks.onError?.(message),
44
- },
45
- label: props.label,
46
- variant: props.variant,
47
- theme: props.theme,
48
- platforms: props.platforms,
49
- description: props.description,
50
- accentColor: props.accentColor,
51
- connectedLabel: props.connectedLabel,
52
- };
53
- }
54
-
55
- /** The inputs whose change should remount the button. */
56
- export function propsIdentity(props: ConnectButtonProps): string {
57
- return JSON.stringify([
58
- props.authorizeUrl,
59
- props.appTheme,
60
- props.label,
61
- props.variant,
62
- props.theme,
63
- props.platforms ?? [],
64
- props.description,
65
- props.accentColor,
66
- props.connectedLabel,
67
- ]);
68
- }