@hyperyai/sdk 1.0.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.
Files changed (102) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +391 -0
  3. package/dist/components/AuthButton.d.ts +51 -0
  4. package/dist/components/AuthButton.js +60 -0
  5. package/dist/components/AuthModal.d.ts +20 -0
  6. package/dist/components/AuthModal.js +62 -0
  7. package/dist/components/BuyButton.d.ts +47 -0
  8. package/dist/components/BuyButton.js +74 -0
  9. package/dist/components/ErrorBoundary.d.ts +13 -0
  10. package/dist/components/ErrorBoundary.js +24 -0
  11. package/dist/components/HyperyModals.d.ts +30 -0
  12. package/dist/components/HyperyModals.js +30 -0
  13. package/dist/components/InsufficientCreditsAlert.d.ts +11 -0
  14. package/dist/components/InsufficientCreditsAlert.js +12 -0
  15. package/dist/components/ModernAuthForm.d.ts +52 -0
  16. package/dist/components/ModernAuthForm.js +83 -0
  17. package/dist/components/RestrictionModal.d.ts +43 -0
  18. package/dist/components/RestrictionModal.js +167 -0
  19. package/dist/components/SignIn.d.ts +26 -0
  20. package/dist/components/SignIn.js +47 -0
  21. package/dist/components/SignInForm.d.ts +41 -0
  22. package/dist/components/SignInForm.js +86 -0
  23. package/dist/components/SignUp.d.ts +26 -0
  24. package/dist/components/SignUp.js +52 -0
  25. package/dist/components/SpendingLimitAlert.d.ts +12 -0
  26. package/dist/components/SpendingLimitAlert.js +14 -0
  27. package/dist/components/UserButton.d.ts +26 -0
  28. package/dist/components/UserButton.js +35 -0
  29. package/dist/components/UserProfile.d.ts +22 -0
  30. package/dist/components/UserProfile.js +26 -0
  31. package/dist/components/WorkspaceSwitcher.d.ts +29 -0
  32. package/dist/components/WorkspaceSwitcher.js +66 -0
  33. package/dist/components/control.d.ts +64 -0
  34. package/dist/components/control.js +89 -0
  35. package/dist/components/ui/dialog.d.ts +19 -0
  36. package/dist/components/ui/dialog.js +53 -0
  37. package/dist/hooks/index.d.ts +22 -0
  38. package/dist/hooks/index.js +23 -0
  39. package/dist/hooks/useBuyerWallet.d.ts +37 -0
  40. package/dist/hooks/useBuyerWallet.js +66 -0
  41. package/dist/hooks/useCheckout.d.ts +27 -0
  42. package/dist/hooks/useCheckout.js +246 -0
  43. package/dist/hooks/useError.d.ts +19 -0
  44. package/dist/hooks/useError.js +36 -0
  45. package/dist/hooks/useMarketplace.d.ts +50 -0
  46. package/dist/hooks/useMarketplace.js +69 -0
  47. package/dist/hooks/useMemberships.d.ts +71 -0
  48. package/dist/hooks/useMemberships.js +138 -0
  49. package/dist/hooks/useWallet.d.ts +62 -0
  50. package/dist/hooks/useWallet.js +123 -0
  51. package/dist/index.d.ts +55 -0
  52. package/dist/index.js +52 -0
  53. package/dist/lib/context.d.ts +50 -0
  54. package/dist/lib/context.js +409 -0
  55. package/dist/lib/oauth.d.ts +49 -0
  56. package/dist/lib/oauth.js +149 -0
  57. package/dist/lib/parse-error.d.ts +45 -0
  58. package/dist/lib/parse-error.js +185 -0
  59. package/dist/lib/parse-stream.d.ts +43 -0
  60. package/dist/lib/parse-stream.js +89 -0
  61. package/dist/lib/popup.d.ts +42 -0
  62. package/dist/lib/popup.js +80 -0
  63. package/dist/lib/storage.d.ts +43 -0
  64. package/dist/lib/storage.js +125 -0
  65. package/dist/lib/utils.d.ts +8 -0
  66. package/dist/lib/utils.js +11 -0
  67. package/dist/types/index.d.ts +191 -0
  68. package/dist/types/index.js +4 -0
  69. package/package.json +78 -0
  70. package/src/components/AuthButton.tsx +123 -0
  71. package/src/components/AuthModal.tsx +240 -0
  72. package/src/components/BuyButton.tsx +150 -0
  73. package/src/components/ErrorBoundary.tsx +97 -0
  74. package/src/components/HyperyModals.tsx +83 -0
  75. package/src/components/InsufficientCreditsAlert.tsx +73 -0
  76. package/src/components/ModernAuthForm.tsx +322 -0
  77. package/src/components/RestrictionModal.tsx +373 -0
  78. package/src/components/SignIn.tsx +84 -0
  79. package/src/components/SignInForm.tsx +256 -0
  80. package/src/components/SignUp.tsx +86 -0
  81. package/src/components/SpendingLimitAlert.tsx +91 -0
  82. package/src/components/UserButton.tsx +106 -0
  83. package/src/components/UserProfile.tsx +106 -0
  84. package/src/components/WorkspaceSwitcher.tsx +199 -0
  85. package/src/components/control.tsx +133 -0
  86. package/src/components/ui/dialog.tsx +151 -0
  87. package/src/hooks/index.ts +65 -0
  88. package/src/hooks/useBuyerWallet.ts +117 -0
  89. package/src/hooks/useCheckout.ts +312 -0
  90. package/src/hooks/useError.ts +60 -0
  91. package/src/hooks/useMarketplace.ts +129 -0
  92. package/src/hooks/useMemberships.ts +203 -0
  93. package/src/hooks/useWallet.ts +170 -0
  94. package/src/index.ts +147 -0
  95. package/src/lib/context.tsx +478 -0
  96. package/src/lib/oauth.ts +215 -0
  97. package/src/lib/parse-error.ts +224 -0
  98. package/src/lib/parse-stream.ts +121 -0
  99. package/src/lib/popup.ts +98 -0
  100. package/src/lib/storage.ts +140 -0
  101. package/src/lib/utils.ts +14 -0
  102. package/src/types/index.ts +216 -0
@@ -0,0 +1,150 @@
1
+ /**
2
+ * BuyButton — "buy with Hypery" marketplace checkout button.
3
+ *
4
+ * On click it runs the full auth+charge flow via {@link useCheckout}: it logs
5
+ * the buyer in if needed, charges, and adds a card if none is on file — each
6
+ * step as a popup or a redirect per `config.interactionMode` (`auto` by default).
7
+ * So an unauthenticated visitor can click Buy and be walked through login → card
8
+ * → charge without leaving the page (popup mode). Never throws.
9
+ */
10
+
11
+ "use client";
12
+
13
+ import type React from "react";
14
+ import { useState } from "react";
15
+ import { type BuySuccess } from "../hooks/useMarketplace";
16
+ import { useCheckout } from "../hooks/useCheckout";
17
+ import type { BrandingConfig, ParsedError } from "../types";
18
+
19
+ export interface BuyButtonProps {
20
+ /** Seller app the purchase is credited to. */
21
+ appId: string;
22
+ /** Amount to charge the buyer, in cents. */
23
+ amountCents: number;
24
+ /** Optional human-readable description of the purchase. */
25
+ description?: string;
26
+ /** Button label. Defaults to a price-formatted "Buy · $X.XX". */
27
+ label?: React.ReactNode;
28
+ /**
29
+ * Require an explicit per-transaction confirmation before charging (the card
30
+ * is charged off-session, so this gives the buyer a clear "yes"). Defaults to
31
+ * true: the first click arms the button ("Confirm · $X.XX" + Cancel), the
32
+ * second click charges. Set false to charge on a single click.
33
+ */
34
+ requireConfirmation?: boolean;
35
+ /** Called with the successful checkout result. */
36
+ onSuccess?: (result: BuySuccess) => void;
37
+ /** Called with a classified error for failures other than needs-a-card. */
38
+ onError?: (error: ParsedError) => void;
39
+ /** Custom className appended to the button. */
40
+ className?: string;
41
+ /** Optional branding (primaryColor is used as the button color). */
42
+ branding?: BrandingConfig;
43
+ }
44
+
45
+ function formatUsd(amountCents: number): string {
46
+ return `$${(amountCents / 100).toFixed(2)}`;
47
+ }
48
+
49
+ /**
50
+ * Drop-in checkout button for the Hypery marketplace.
51
+ *
52
+ * @example
53
+ * ```tsx
54
+ * <BuyButton appId="app_123" amountCents={499} description="Pro upgrade"
55
+ * onSuccess={(r) => console.log('paid', r.paymentIntentId)} />
56
+ * ```
57
+ */
58
+ export function BuyButton({
59
+ appId,
60
+ amountCents,
61
+ description,
62
+ label,
63
+ onSuccess,
64
+ onError,
65
+ className = "",
66
+ branding,
67
+ requireConfirmation = true,
68
+ }: BuyButtonProps) {
69
+ const { checkout, isRunning } = useCheckout();
70
+ const [armed, setArmed] = useState(false);
71
+
72
+ const busy = isRunning;
73
+
74
+ const charge = async () => {
75
+ setArmed(false);
76
+ // Runs login → charge → add-card → retry as needed (popup or redirect).
77
+ const result = await checkout({ kind: "purchase", appId, amountCents, description });
78
+
79
+ if (result.status === "success") {
80
+ const b = result.data || {};
81
+ onSuccess?.({
82
+ ok: true,
83
+ paymentIntentId: b.paymentIntentId,
84
+ status: b.status,
85
+ amountCents: b.amountCents ?? amountCents,
86
+ applicationFeeCents: b.applicationFeeCents ?? 0,
87
+ } as BuySuccess);
88
+ return;
89
+ }
90
+ // 'redirecting' (navigating away, resumes on return) and 'cancelled' (user
91
+ // closed a popup) are not errors — stay quiet.
92
+ if (result.status === "error" && result.error) {
93
+ onError?.(result.error);
94
+ }
95
+ };
96
+
97
+ const handleClick = () => {
98
+ if (requireConfirmation && !armed) {
99
+ setArmed(true);
100
+ return;
101
+ }
102
+ void charge();
103
+ };
104
+
105
+ const primaryColor = branding?.primaryColor;
106
+ const style = primaryColor ? { backgroundColor: primaryColor } : undefined;
107
+
108
+ const idleLabel = label ?? `Buy · ${formatUsd(amountCents)}`;
109
+ const buttonLabel = busy
110
+ ? "Processing…"
111
+ : armed
112
+ ? `Confirm · ${formatUsd(amountCents)}`
113
+ : idleLabel;
114
+
115
+ return (
116
+ <span className="inline-flex items-center gap-2">
117
+ <button
118
+ type="button"
119
+ onClick={handleClick}
120
+ disabled={busy}
121
+ style={style}
122
+ className={`
123
+ inline-flex items-center justify-center gap-2
124
+ px-6 py-2.5 text-base rounded-lg font-medium transition-all
125
+ text-white shadow-md hover:shadow-lg
126
+ ${primaryColor ? "" : "bg-purple-600 hover:bg-purple-700"}
127
+ disabled:opacity-60 disabled:cursor-not-allowed
128
+ ${className}
129
+ `}
130
+ >
131
+ {busy && (
132
+ <span
133
+ className="inline-block h-4 w-4 animate-spin rounded-full border-2 border-white/40 border-t-white"
134
+ aria-hidden="true"
135
+ />
136
+ )}
137
+ {buttonLabel}
138
+ </button>
139
+ {armed && !busy && (
140
+ <button
141
+ type="button"
142
+ onClick={() => setArmed(false)}
143
+ className="px-3 py-2.5 text-sm rounded-lg font-medium text-gray-600 hover:text-gray-900 hover:bg-gray-100 transition-colors"
144
+ >
145
+ Cancel
146
+ </button>
147
+ )}
148
+ </span>
149
+ );
150
+ }
@@ -0,0 +1,97 @@
1
+ 'use client';
2
+
3
+ import { parseError } from '../lib/parse-error';
4
+ import { SpendingLimitAlert } from './SpendingLimitAlert';
5
+ import { InsufficientCreditsAlert } from './InsufficientCreditsAlert';
6
+
7
+ export interface ErrorBoundaryProps {
8
+ error: any;
9
+ onRetry?: () => void;
10
+ onUpgradeLimits?: () => void;
11
+ onAddCredits?: () => void;
12
+ className?: string;
13
+ children?: React.ReactNode;
14
+ }
15
+
16
+ /**
17
+ * Universal error boundary component
18
+ * Automatically renders the appropriate error UI based on error type
19
+ */
20
+ export function ErrorBoundary({
21
+ error,
22
+ onRetry,
23
+ onUpgradeLimits,
24
+ onAddCredits,
25
+ className = '',
26
+ children,
27
+ }: ErrorBoundaryProps) {
28
+ if (!error) return <>{children}</>;
29
+
30
+ const parsed = parseError(error);
31
+
32
+ // Render spending limit alert
33
+ if (parsed.isSpendingLimit) {
34
+ return (
35
+ <SpendingLimitAlert
36
+ error={parsed}
37
+ onRetry={onRetry}
38
+ onUpgradeLimits={onUpgradeLimits}
39
+ className={className}
40
+ />
41
+ );
42
+ }
43
+
44
+ // Render insufficient credits alert
45
+ if (parsed.isInsufficientCredits) {
46
+ return (
47
+ <InsufficientCreditsAlert
48
+ error={parsed}
49
+ onAddCredits={onAddCredits}
50
+ className={className}
51
+ />
52
+ );
53
+ }
54
+
55
+ // Render generic error
56
+ return (
57
+ <div
58
+ className={`rounded-lg border border-gray-200 bg-gray-50 p-4 ${className}`}
59
+ role="alert"
60
+ >
61
+ <div className="flex items-start gap-3">
62
+ <div className="flex-shrink-0">
63
+ <svg
64
+ className="h-5 w-5 text-gray-400"
65
+ viewBox="0 0 20 20"
66
+ fill="currentColor"
67
+ aria-hidden="true"
68
+ >
69
+ <path
70
+ fillRule="evenodd"
71
+ d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z"
72
+ clipRule="evenodd"
73
+ />
74
+ </svg>
75
+ </div>
76
+ <div className="flex-1">
77
+ <h3 className="text-sm font-medium text-gray-800">Error</h3>
78
+ <div className="mt-2 text-sm text-gray-700">
79
+ <p>{parsed.message}</p>
80
+ </div>
81
+ {onRetry && (
82
+ <div className="mt-4">
83
+ <button
84
+ type="button"
85
+ onClick={onRetry}
86
+ className="text-sm font-medium text-gray-800 hover:text-gray-900"
87
+ >
88
+ Try again
89
+ </button>
90
+ </div>
91
+ )}
92
+ </div>
93
+ </div>
94
+ </div>
95
+ );
96
+ }
97
+
@@ -0,0 +1,83 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { useHyperyAuth } from "../lib/context";
5
+ import { AuthModal } from "./AuthModal";
6
+ import { type RestrictionError, RestrictionModal } from "./RestrictionModal";
7
+
8
+ export interface HyperyModalsProps {
9
+ /**
10
+ * Branding + auth-form options forwarded to the auto-opened `AuthModal`.
11
+ */
12
+ branding?: {
13
+ logo?: string;
14
+ appName?: string;
15
+ primaryColor?: string;
16
+ };
17
+ showSocial?: boolean;
18
+ showEmailPassword?: boolean;
19
+ /**
20
+ * Called when the funds modal's retry button is pressed (e.g. re-run the
21
+ * request that was blocked). The restriction is cleared automatically.
22
+ */
23
+ onRetry?: () => void;
24
+ }
25
+
26
+ /**
27
+ * Turnkey modal host. Mount this ONCE anywhere inside `<HyperyProvider>` and the
28
+ * billing/auth modals open automatically off the context state that
29
+ * `authenticatedFetch` sets — no manual `setError`/`isOpen` wiring:
30
+ *
31
+ * - a 402/429 billing restriction → `<RestrictionModal>` (add funds / add card)
32
+ * - a 401 that survives a silent-refresh retry → `<AuthModal>` (re-auth)
33
+ *
34
+ * All wiring (clientId, gatewayUrl, getAccessToken, open/close) is read from
35
+ * context, so this component takes no required props.
36
+ */
37
+ export function HyperyModals({
38
+ branding,
39
+ showSocial,
40
+ showEmailPassword,
41
+ onRetry,
42
+ }: HyperyModalsProps = {}) {
43
+ const {
44
+ clientId,
45
+ gatewayUrl,
46
+ getAccessToken,
47
+ restriction,
48
+ clearRestriction,
49
+ authRequired,
50
+ clearAuthRequired,
51
+ } = useHyperyAuth();
52
+
53
+ // ParsedError → RestrictionModal's RestrictionError (data carries the billing
54
+ // fields: limit/current/available/resetsAt/...).
55
+ const restrictionError: RestrictionError | null = restriction
56
+ ? {
57
+ code: restriction.code,
58
+ message: restriction.message,
59
+ type: restriction.type,
60
+ ...(restriction.data as Record<string, unknown>),
61
+ }
62
+ : null;
63
+
64
+ return (
65
+ <>
66
+ <RestrictionModal
67
+ error={restrictionError}
68
+ clientId={clientId}
69
+ gatewayUrl={gatewayUrl}
70
+ getAccessToken={getAccessToken}
71
+ onClose={clearRestriction}
72
+ onRetry={onRetry}
73
+ />
74
+ <AuthModal
75
+ isOpen={authRequired}
76
+ onClose={clearAuthRequired}
77
+ branding={branding}
78
+ showSocial={showSocial}
79
+ showEmailPassword={showEmailPassword}
80
+ />
81
+ </>
82
+ );
83
+ }
@@ -0,0 +1,73 @@
1
+ 'use client';
2
+
3
+ import { ParsedError } from '../types';
4
+
5
+ export interface InsufficientCreditsAlertProps {
6
+ error: ParsedError;
7
+ onAddCredits?: () => void;
8
+ className?: string;
9
+ }
10
+
11
+ /**
12
+ * Alert component for insufficient credits errors
13
+ * Displays a user-friendly message with option to add credits
14
+ */
15
+ export function InsufficientCreditsAlert({
16
+ error,
17
+ onAddCredits,
18
+ className = '',
19
+ }: InsufficientCreditsAlertProps) {
20
+ if (!error.isInsufficientCredits) return null;
21
+
22
+ const data = error.data as any;
23
+
24
+ return (
25
+ <div
26
+ className={`rounded-lg border border-red-200 bg-red-50 p-4 ${className}`}
27
+ role="alert"
28
+ >
29
+ <div className="flex items-start gap-3">
30
+ <div className="flex-shrink-0">
31
+ <svg
32
+ className="h-5 w-5 text-red-400"
33
+ viewBox="0 0 20 20"
34
+ fill="currentColor"
35
+ aria-hidden="true"
36
+ >
37
+ <path
38
+ fillRule="evenodd"
39
+ d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z"
40
+ clipRule="evenodd"
41
+ />
42
+ </svg>
43
+ </div>
44
+ <div className="flex-1">
45
+ <h3 className="text-sm font-medium text-red-800">
46
+ Insufficient Credits
47
+ </h3>
48
+ <div className="mt-2 text-sm text-red-700">
49
+ <p>{error.message}</p>
50
+ {data.available !== undefined && data.required !== undefined && (
51
+ <p className="mt-1">
52
+ You have <strong>{data.available}</strong> credits, but need{' '}
53
+ <strong>{data.required}</strong> credits.
54
+ </p>
55
+ )}
56
+ </div>
57
+ {onAddCredits && (
58
+ <div className="mt-4">
59
+ <button
60
+ type="button"
61
+ onClick={onAddCredits}
62
+ className="text-sm font-medium text-red-800 hover:text-red-900 underline"
63
+ >
64
+ Add credits →
65
+ </button>
66
+ </div>
67
+ )}
68
+ </div>
69
+ </div>
70
+ </div>
71
+ );
72
+ }
73
+