@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,74 @@
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
+ "use client";
11
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
+ import { useState } from "react";
13
+ import { useCheckout } from "../hooks/useCheckout";
14
+ function formatUsd(amountCents) {
15
+ return `$${(amountCents / 100).toFixed(2)}`;
16
+ }
17
+ /**
18
+ * Drop-in checkout button for the Hypery marketplace.
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * <BuyButton appId="app_123" amountCents={499} description="Pro upgrade"
23
+ * onSuccess={(r) => console.log('paid', r.paymentIntentId)} />
24
+ * ```
25
+ */
26
+ export function BuyButton({ appId, amountCents, description, label, onSuccess, onError, className = "", branding, requireConfirmation = true, }) {
27
+ const { checkout, isRunning } = useCheckout();
28
+ const [armed, setArmed] = useState(false);
29
+ const busy = isRunning;
30
+ const charge = async () => {
31
+ setArmed(false);
32
+ // Runs login → charge → add-card → retry as needed (popup or redirect).
33
+ const result = await checkout({ kind: "purchase", appId, amountCents, description });
34
+ if (result.status === "success") {
35
+ const b = result.data || {};
36
+ onSuccess?.({
37
+ ok: true,
38
+ paymentIntentId: b.paymentIntentId,
39
+ status: b.status,
40
+ amountCents: b.amountCents ?? amountCents,
41
+ applicationFeeCents: b.applicationFeeCents ?? 0,
42
+ });
43
+ return;
44
+ }
45
+ // 'redirecting' (navigating away, resumes on return) and 'cancelled' (user
46
+ // closed a popup) are not errors — stay quiet.
47
+ if (result.status === "error" && result.error) {
48
+ onError?.(result.error);
49
+ }
50
+ };
51
+ const handleClick = () => {
52
+ if (requireConfirmation && !armed) {
53
+ setArmed(true);
54
+ return;
55
+ }
56
+ void charge();
57
+ };
58
+ const primaryColor = branding?.primaryColor;
59
+ const style = primaryColor ? { backgroundColor: primaryColor } : undefined;
60
+ const idleLabel = label ?? `Buy · ${formatUsd(amountCents)}`;
61
+ const buttonLabel = busy
62
+ ? "Processing…"
63
+ : armed
64
+ ? `Confirm · ${formatUsd(amountCents)}`
65
+ : idleLabel;
66
+ return (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsxs("button", { type: "button", onClick: handleClick, disabled: busy, style: style, className: `
67
+ inline-flex items-center justify-center gap-2
68
+ px-6 py-2.5 text-base rounded-lg font-medium transition-all
69
+ text-white shadow-md hover:shadow-lg
70
+ ${primaryColor ? "" : "bg-purple-600 hover:bg-purple-700"}
71
+ disabled:opacity-60 disabled:cursor-not-allowed
72
+ ${className}
73
+ `, children: [busy && (_jsx("span", { className: "inline-block h-4 w-4 animate-spin rounded-full border-2 border-white/40 border-t-white", "aria-hidden": "true" })), buttonLabel] }), armed && !busy && (_jsx("button", { type: "button", onClick: () => setArmed(false), 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", children: "Cancel" }))] }));
74
+ }
@@ -0,0 +1,13 @@
1
+ export interface ErrorBoundaryProps {
2
+ error: any;
3
+ onRetry?: () => void;
4
+ onUpgradeLimits?: () => void;
5
+ onAddCredits?: () => void;
6
+ className?: string;
7
+ children?: React.ReactNode;
8
+ }
9
+ /**
10
+ * Universal error boundary component
11
+ * Automatically renders the appropriate error UI based on error type
12
+ */
13
+ export declare function ErrorBoundary({ error, onRetry, onUpgradeLimits, onAddCredits, className, children, }: ErrorBoundaryProps): import("react").JSX.Element;
@@ -0,0 +1,24 @@
1
+ 'use client';
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { parseError } from '../lib/parse-error';
4
+ import { SpendingLimitAlert } from './SpendingLimitAlert';
5
+ import { InsufficientCreditsAlert } from './InsufficientCreditsAlert';
6
+ /**
7
+ * Universal error boundary component
8
+ * Automatically renders the appropriate error UI based on error type
9
+ */
10
+ export function ErrorBoundary({ error, onRetry, onUpgradeLimits, onAddCredits, className = '', children, }) {
11
+ if (!error)
12
+ return _jsx(_Fragment, { children: children });
13
+ const parsed = parseError(error);
14
+ // Render spending limit alert
15
+ if (parsed.isSpendingLimit) {
16
+ return (_jsx(SpendingLimitAlert, { error: parsed, onRetry: onRetry, onUpgradeLimits: onUpgradeLimits, className: className }));
17
+ }
18
+ // Render insufficient credits alert
19
+ if (parsed.isInsufficientCredits) {
20
+ return (_jsx(InsufficientCreditsAlert, { error: parsed, onAddCredits: onAddCredits, className: className }));
21
+ }
22
+ // Render generic error
23
+ return (_jsx("div", { className: `rounded-lg border border-gray-200 bg-gray-50 p-4 ${className}`, role: "alert", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex-shrink-0", children: _jsx("svg", { className: "h-5 w-5 text-gray-400", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { fillRule: "evenodd", 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", clipRule: "evenodd" }) }) }), _jsxs("div", { className: "flex-1", children: [_jsx("h3", { className: "text-sm font-medium text-gray-800", children: "Error" }), _jsx("div", { className: "mt-2 text-sm text-gray-700", children: _jsx("p", { children: parsed.message }) }), onRetry && (_jsx("div", { className: "mt-4", children: _jsx("button", { type: "button", onClick: onRetry, className: "text-sm font-medium text-gray-800 hover:text-gray-900", children: "Try again" }) }))] })] }) }));
24
+ }
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ export interface HyperyModalsProps {
3
+ /**
4
+ * Branding + auth-form options forwarded to the auto-opened `AuthModal`.
5
+ */
6
+ branding?: {
7
+ logo?: string;
8
+ appName?: string;
9
+ primaryColor?: string;
10
+ };
11
+ showSocial?: boolean;
12
+ showEmailPassword?: boolean;
13
+ /**
14
+ * Called when the funds modal's retry button is pressed (e.g. re-run the
15
+ * request that was blocked). The restriction is cleared automatically.
16
+ */
17
+ onRetry?: () => void;
18
+ }
19
+ /**
20
+ * Turnkey modal host. Mount this ONCE anywhere inside `<HyperyProvider>` and the
21
+ * billing/auth modals open automatically off the context state that
22
+ * `authenticatedFetch` sets — no manual `setError`/`isOpen` wiring:
23
+ *
24
+ * - a 402/429 billing restriction → `<RestrictionModal>` (add funds / add card)
25
+ * - a 401 that survives a silent-refresh retry → `<AuthModal>` (re-auth)
26
+ *
27
+ * All wiring (clientId, gatewayUrl, getAccessToken, open/close) is read from
28
+ * context, so this component takes no required props.
29
+ */
30
+ export declare function HyperyModals({ branding, showSocial, showEmailPassword, onRetry, }?: HyperyModalsProps): React.JSX.Element;
@@ -0,0 +1,30 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useHyperyAuth } from "../lib/context";
4
+ import { AuthModal } from "./AuthModal";
5
+ import { RestrictionModal } from "./RestrictionModal";
6
+ /**
7
+ * Turnkey modal host. Mount this ONCE anywhere inside `<HyperyProvider>` and the
8
+ * billing/auth modals open automatically off the context state that
9
+ * `authenticatedFetch` sets — no manual `setError`/`isOpen` wiring:
10
+ *
11
+ * - a 402/429 billing restriction → `<RestrictionModal>` (add funds / add card)
12
+ * - a 401 that survives a silent-refresh retry → `<AuthModal>` (re-auth)
13
+ *
14
+ * All wiring (clientId, gatewayUrl, getAccessToken, open/close) is read from
15
+ * context, so this component takes no required props.
16
+ */
17
+ export function HyperyModals({ branding, showSocial, showEmailPassword, onRetry, } = {}) {
18
+ const { clientId, gatewayUrl, getAccessToken, restriction, clearRestriction, authRequired, clearAuthRequired, } = useHyperyAuth();
19
+ // ParsedError → RestrictionModal's RestrictionError (data carries the billing
20
+ // fields: limit/current/available/resetsAt/...).
21
+ const restrictionError = restriction
22
+ ? {
23
+ code: restriction.code,
24
+ message: restriction.message,
25
+ type: restriction.type,
26
+ ...restriction.data,
27
+ }
28
+ : null;
29
+ return (_jsxs(_Fragment, { children: [_jsx(RestrictionModal, { error: restrictionError, clientId: clientId, gatewayUrl: gatewayUrl, getAccessToken: getAccessToken, onClose: clearRestriction, onRetry: onRetry }), _jsx(AuthModal, { isOpen: authRequired, onClose: clearAuthRequired, branding: branding, showSocial: showSocial, showEmailPassword: showEmailPassword })] }));
30
+ }
@@ -0,0 +1,11 @@
1
+ import { ParsedError } from '../types';
2
+ export interface InsufficientCreditsAlertProps {
3
+ error: ParsedError;
4
+ onAddCredits?: () => void;
5
+ className?: string;
6
+ }
7
+ /**
8
+ * Alert component for insufficient credits errors
9
+ * Displays a user-friendly message with option to add credits
10
+ */
11
+ export declare function InsufficientCreditsAlert({ error, onAddCredits, className, }: InsufficientCreditsAlertProps): import("react").JSX.Element | null;
@@ -0,0 +1,12 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Alert component for insufficient credits errors
5
+ * Displays a user-friendly message with option to add credits
6
+ */
7
+ export function InsufficientCreditsAlert({ error, onAddCredits, className = '', }) {
8
+ if (!error.isInsufficientCredits)
9
+ return null;
10
+ const data = error.data;
11
+ return (_jsx("div", { className: `rounded-lg border border-red-200 bg-red-50 p-4 ${className}`, role: "alert", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex-shrink-0", children: _jsx("svg", { className: "h-5 w-5 text-red-400", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { fillRule: "evenodd", 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", clipRule: "evenodd" }) }) }), _jsxs("div", { className: "flex-1", children: [_jsx("h3", { className: "text-sm font-medium text-red-800", children: "Insufficient Credits" }), _jsxs("div", { className: "mt-2 text-sm text-red-700", children: [_jsx("p", { children: error.message }), data.available !== undefined && data.required !== undefined && (_jsxs("p", { className: "mt-1", children: ["You have ", _jsx("strong", { children: data.available }), " credits, but need", ' ', _jsx("strong", { children: data.required }), " credits."] }))] }), onAddCredits && (_jsx("div", { className: "mt-4", children: _jsx("button", { type: "button", onClick: onAddCredits, className: "text-sm font-medium text-red-800 hover:text-red-900 underline", children: "Add credits \u2192" }) }))] })] }) }));
12
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * ModernAuthForm Component
3
+ * Beautiful, embeddable authentication form
4
+ * Inspired by modern SaaS login pages
5
+ * Can be used standalone on login/signup pages
6
+ */
7
+ import React from 'react';
8
+ export interface ModernAuthFormProps {
9
+ /** Auth mode: signin or signup */
10
+ mode?: 'signin' | 'signup';
11
+ /** Allow switching between signin/signup */
12
+ allowModeSwitch?: boolean;
13
+ /** Custom className */
14
+ className?: string;
15
+ /** Show card wrapper */
16
+ showCard?: boolean;
17
+ /** Show social OAuth buttons */
18
+ showSocial?: boolean;
19
+ /** Show email/password form */
20
+ showEmailPassword?: boolean;
21
+ /** Callback after successful auth */
22
+ onSuccess?: () => void;
23
+ /** Callback on error */
24
+ onError?: (error: string) => void;
25
+ /** Custom branding */
26
+ branding?: {
27
+ logo?: string;
28
+ appName?: string;
29
+ primaryColor?: string;
30
+ };
31
+ }
32
+ /**
33
+ * Modern authentication form with beautiful design
34
+ * Perfect for dedicated login/signup pages
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * // On login page
39
+ * <ModernAuthForm
40
+ * mode="signin"
41
+ * showCard
42
+ * allowModeSwitch
43
+ * onSuccess={() => router.push('/dashboard')}
44
+ * branding={{
45
+ * logo: '/logo.png',
46
+ * appName: 'My App',
47
+ * primaryColor: '#8b5cf6'
48
+ * }}
49
+ * />
50
+ * ```
51
+ */
52
+ export declare function ModernAuthForm({ mode: initialMode, allowModeSwitch, className, showCard, showSocial, showEmailPassword, onSuccess, onError, branding, }: ModernAuthFormProps): React.JSX.Element;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * ModernAuthForm Component
3
+ * Beautiful, embeddable authentication form
4
+ * Inspired by modern SaaS login pages
5
+ * Can be used standalone on login/signup pages
6
+ */
7
+ 'use client';
8
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
+ import { useState } from 'react';
10
+ import { useHyperyAuth } from '../lib/context';
11
+ /**
12
+ * Modern authentication form with beautiful design
13
+ * Perfect for dedicated login/signup pages
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * // On login page
18
+ * <ModernAuthForm
19
+ * mode="signin"
20
+ * showCard
21
+ * allowModeSwitch
22
+ * onSuccess={() => router.push('/dashboard')}
23
+ * branding={{
24
+ * logo: '/logo.png',
25
+ * appName: 'My App',
26
+ * primaryColor: '#8b5cf6'
27
+ * }}
28
+ * />
29
+ * ```
30
+ */
31
+ export function ModernAuthForm({ mode: initialMode = 'signin', allowModeSwitch = true, className = '', showCard = true, showSocial = true, showEmailPassword = true, onSuccess, onError, branding, }) {
32
+ const { login } = useHyperyAuth();
33
+ const [mode, setMode] = useState(initialMode);
34
+ const [isLoading, setIsLoading] = useState(false);
35
+ const [loadingProvider, setLoadingProvider] = useState(null);
36
+ const [email, setEmail] = useState('');
37
+ const [password, setPassword] = useState('');
38
+ const [name, setName] = useState('');
39
+ const [error, setError] = useState('');
40
+ const handleSocialSignIn = (provider) => {
41
+ setLoadingProvider(provider);
42
+ setError('');
43
+ try {
44
+ login();
45
+ if (onSuccess) {
46
+ onSuccess();
47
+ }
48
+ }
49
+ catch (err) {
50
+ const errorMessage = err instanceof Error ? err.message : `${provider} sign in failed`;
51
+ setError(errorMessage);
52
+ if (onError) {
53
+ onError(errorMessage);
54
+ }
55
+ setLoadingProvider(null);
56
+ }
57
+ };
58
+ const handleEmailSubmit = (e) => {
59
+ e.preventDefault();
60
+ setError('');
61
+ setIsLoading(true);
62
+ if (!email || !password) {
63
+ setError('Please fill in all fields');
64
+ setIsLoading(false);
65
+ return;
66
+ }
67
+ if (mode === 'signup' && !name) {
68
+ setError('Please enter your name');
69
+ setIsLoading(false);
70
+ return;
71
+ }
72
+ setError('Direct email authentication coming soon. Please use social sign-in.');
73
+ setIsLoading(false);
74
+ };
75
+ const primaryColor = branding?.primaryColor || '#8b5cf6';
76
+ const formContent = (_jsxs("div", { className: !showCard ? className : '', children: [_jsxs("div", { className: "text-center mb-8", children: [branding?.logo && (_jsx("img", { src: branding.logo, alt: "Logo", className: "h-12 mx-auto mb-6" })), _jsx("h1", { className: "text-3xl font-bold text-gray-900 dark:text-white mb-3", children: mode === 'signin' ? 'Welcome back' : 'Create your account' }), _jsx("p", { className: "text-gray-600 dark:text-gray-400", children: mode === 'signin'
77
+ ? `Sign in to ${branding?.appName || 'continue'}`
78
+ : `Get started with ${branding?.appName || 'your account'}` })] }), error && (_jsxs("div", { className: "mb-6 p-4 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-800 dark:text-red-200 text-sm flex items-start gap-3", children: [_jsx("svg", { className: "w-5 h-5 flex-shrink-0 mt-0.5", fill: "currentColor", viewBox: "0 0 20 20", children: _jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z", clipRule: "evenodd" }) }), _jsx("span", { children: error })] })), showSocial && (_jsxs("div", { className: "space-y-3 mb-6", children: [_jsxs("button", { onClick: () => handleSocialSignIn('google'), disabled: !!loadingProvider, className: "w-full bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-750 border border-gray-300 dark:border-gray-700 text-gray-900 dark:text-white py-3.5 px-4 rounded-xl flex items-center justify-center gap-3 font-medium transition-all disabled:opacity-50 shadow-sm hover:shadow-md", children: [loadingProvider === 'google' ? (_jsx("div", { className: "w-5 h-5 border-2 border-gray-300 border-t-transparent rounded-full animate-spin" })) : (_jsxs("svg", { className: "w-5 h-5", viewBox: "0 0 24 24", children: [_jsx("path", { fill: "#4285F4", d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" }), _jsx("path", { fill: "#34A853", d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" }), _jsx("path", { fill: "#FBBC05", d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" }), _jsx("path", { fill: "#EA4335", d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" })] })), _jsx("span", { children: "Continue with Google" })] }), _jsxs("button", { onClick: () => handleSocialSignIn('github'), disabled: !!loadingProvider, className: "w-full bg-gray-900 dark:bg-gray-800 hover:bg-gray-800 dark:hover:bg-gray-750 text-white py-3.5 px-4 rounded-xl flex items-center justify-center gap-3 font-medium transition-all disabled:opacity-50 shadow-sm hover:shadow-md", children: [loadingProvider === 'github' ? (_jsx("div", { className: "w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" })) : (_jsx("svg", { className: "w-5 h-5", fill: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { fillRule: "evenodd", d: "M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z", clipRule: "evenodd" }) })), _jsx("span", { children: "Continue with GitHub" })] })] })), showSocial && showEmailPassword && (_jsxs("div", { className: "relative mb-6", children: [_jsx("div", { className: "absolute inset-0 flex items-center", children: _jsx("div", { className: "w-full border-t border-gray-300 dark:border-gray-700" }) }), _jsx("div", { className: "relative flex justify-center text-sm", children: _jsx("span", { className: "px-4 bg-white dark:bg-gray-900 text-gray-500 dark:text-gray-400 font-medium", children: "Or continue with email" }) })] })), showEmailPassword && (_jsxs("form", { onSubmit: handleEmailSubmit, className: "space-y-5", children: [mode === 'signup' && (_jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2", children: "Full name" }), _jsx("input", { type: "text", value: name, onChange: (e) => setName(e.target.value), className: "w-full px-4 py-3.5 rounded-xl border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-offset-2 focus:outline-none transition-all", style: { '--tw-ring-color': primaryColor }, placeholder: "John Doe", disabled: isLoading })] })), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2", children: "Email address" }), _jsx("input", { type: "email", value: email, onChange: (e) => setEmail(e.target.value), className: "w-full px-4 py-3.5 rounded-xl border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-offset-2 focus:outline-none transition-all", style: { '--tw-ring-color': primaryColor }, placeholder: "you@example.com", disabled: isLoading, autoComplete: "email" })] }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300", children: "Password" }), mode === 'signin' && (_jsx("a", { href: "/forgot-password", className: "text-sm font-medium hover:underline", style: { color: primaryColor }, children: "Forgot?" }))] }), _jsx("input", { type: "password", value: password, onChange: (e) => setPassword(e.target.value), className: "w-full px-4 py-3.5 rounded-xl border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-offset-2 focus:outline-none transition-all", style: { '--tw-ring-color': primaryColor }, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", disabled: isLoading, autoComplete: mode === 'signin' ? 'current-password' : 'new-password' })] }), _jsx("button", { type: "submit", disabled: isLoading, className: "w-full py-3.5 px-4 rounded-xl font-semibold text-white shadow-lg hover:shadow-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed transform hover:scale-[1.02] active:scale-[0.98]", style: { backgroundColor: primaryColor }, children: isLoading ? (_jsxs("span", { className: "flex items-center justify-center gap-2", children: [_jsx("div", { className: "w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" }), mode === 'signin' ? 'Signing in...' : 'Creating account...'] })) : (mode === 'signin' ? 'Sign in' : 'Create account') })] })), allowModeSwitch && (_jsxs("div", { className: "mt-6 text-center text-sm", children: [_jsx("span", { className: "text-gray-600 dark:text-gray-400", children: mode === 'signin' ? "Don't have an account?" : 'Already have an account?' }), ' ', _jsx("button", { onClick: () => setMode(mode === 'signin' ? 'signup' : 'signin'), className: "font-semibold hover:underline", style: { color: primaryColor }, children: mode === 'signin' ? 'Sign up' : 'Sign in' })] })), _jsxs("div", { className: "mt-8 pt-6 border-t border-gray-200 dark:border-gray-800 text-center text-xs text-gray-500 dark:text-gray-500", children: ["By continuing, you agree to our", ' ', _jsx("a", { href: "/terms", className: "hover:underline font-medium", style: { color: primaryColor }, children: "Terms of Service" }), ' ', "and", ' ', _jsx("a", { href: "/privacy", className: "hover:underline font-medium", style: { color: primaryColor }, children: "Privacy Policy" })] })] }));
79
+ if (!showCard) {
80
+ return _jsx("div", { className: className, children: formContent });
81
+ }
82
+ return (_jsx("div", { className: `bg-white dark:bg-gray-900 rounded-2xl shadow-2xl p-10 max-w-md w-full ${className}`, children: formContent }));
83
+ }
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ export interface RestrictionError {
3
+ code: string;
4
+ message: string;
5
+ type?: string;
6
+ limitType?: 'daily' | 'monthly' | 'total';
7
+ limit?: number;
8
+ current?: number;
9
+ requested?: number;
10
+ resetsAt?: string;
11
+ available?: number;
12
+ required?: number;
13
+ [key: string]: any;
14
+ }
15
+ interface RestrictionModalProps {
16
+ error: RestrictionError | null;
17
+ /** OAuth clientId of the app making the requests. Preferred name. */
18
+ clientId?: string;
19
+ /**
20
+ * @deprecated Use `clientId` instead. This prop is actually the OAuth clientId,
21
+ * not the DB app id. Kept as a backward-compatible alias.
22
+ */
23
+ appId?: string;
24
+ gatewayUrl: string;
25
+ getAccessToken: () => Promise<string | null>;
26
+ onClose: () => void;
27
+ onRetry?: () => void;
28
+ /** Called after funds/card are successfully added. */
29
+ onFunded?: () => void;
30
+ className?: string;
31
+ overlayClassName?: string;
32
+ }
33
+ /**
34
+ * Mode-aware restriction / funds modal.
35
+ *
36
+ * Reads the team's wallet state (GET /api/wallet/state) and renders the CTA that
37
+ * matches the billing lever:
38
+ * - metered + no card → "Add a payment method" (Stripe-hosted Checkout)
39
+ * - metered + declined → "Update payment method"
40
+ * - prepaid → 1-click "Add $X" (charges the saved card in place)
41
+ */
42
+ export declare function RestrictionModal({ error, clientId, appId, gatewayUrl, getAccessToken, onClose, onRetry, onFunded, className, overlayClassName, }: RestrictionModalProps): React.JSX.Element | null;
43
+ export {};
@@ -0,0 +1,167 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useState } from 'react';
4
+ const QUICK_AMOUNTS = [10, 25, 50];
5
+ /**
6
+ * Mode-aware restriction / funds modal.
7
+ *
8
+ * Reads the team's wallet state (GET /api/wallet/state) and renders the CTA that
9
+ * matches the billing lever:
10
+ * - metered + no card → "Add a payment method" (Stripe-hosted Checkout)
11
+ * - metered + declined → "Update payment method"
12
+ * - prepaid → 1-click "Add $X" (charges the saved card in place)
13
+ */
14
+ export function RestrictionModal({ error, clientId, appId, gatewayUrl, getAccessToken, onClose, onRetry, onFunded, className = '', overlayClassName = '', }) {
15
+ // `appId` is a deprecated alias for `clientId`; prefer `clientId` when both are set.
16
+ const resolvedClientId = clientId ?? appId;
17
+ const [wallet, setWallet] = useState(null);
18
+ const [isLoading, setIsLoading] = useState(true);
19
+ const [fetchError, setFetchError] = useState(null);
20
+ const [busy, setBusy] = useState(false);
21
+ const [actionError, setActionError] = useState(null);
22
+ const [funded, setFunded] = useState(false);
23
+ const authHeaders = useCallback(async () => {
24
+ const token = await getAccessToken();
25
+ if (!token)
26
+ throw new Error('Not authenticated');
27
+ const headers = {
28
+ Authorization: `Bearer ${token}`,
29
+ 'Content-Type': 'application/json',
30
+ };
31
+ if (resolvedClientId)
32
+ headers['X-Hypery-Client-Id'] = resolvedClientId;
33
+ return headers;
34
+ }, [getAccessToken, resolvedClientId]);
35
+ const loadWallet = useCallback(async () => {
36
+ try {
37
+ setIsLoading(true);
38
+ setFetchError(null);
39
+ const headers = await authHeaders();
40
+ const res = await fetch(`${gatewayUrl}/api/wallet/state`, { headers });
41
+ if (!res.ok)
42
+ throw new Error(`Failed to load wallet (${res.status})`);
43
+ setWallet((await res.json()));
44
+ }
45
+ catch (err) {
46
+ setFetchError(err instanceof Error ? err.message : 'Failed to load wallet');
47
+ }
48
+ finally {
49
+ setIsLoading(false);
50
+ }
51
+ }, [authHeaders, gatewayUrl]);
52
+ useEffect(() => {
53
+ if (!error)
54
+ return;
55
+ void loadWallet();
56
+ }, [error, loadWallet]);
57
+ // 1-click: charge the saved card and credit the team.
58
+ const addFunds = useCallback(async (usd) => {
59
+ setBusy(true);
60
+ setActionError(null);
61
+ try {
62
+ const headers = await authHeaders();
63
+ const res = await fetch(`${gatewayUrl}/api/wallet/topup`, {
64
+ method: 'POST',
65
+ headers,
66
+ body: JSON.stringify({ amount: usd }),
67
+ });
68
+ const body = await res.json().catch(() => ({}));
69
+ if (!res.ok || !body?.success) {
70
+ throw new Error(body?.error || `Top-up failed (${res.status})`);
71
+ }
72
+ await loadWallet();
73
+ setFunded(true);
74
+ onFunded?.();
75
+ }
76
+ catch (err) {
77
+ setActionError(err instanceof Error ? err.message : 'Top-up failed');
78
+ }
79
+ finally {
80
+ setBusy(false);
81
+ }
82
+ }, [authHeaders, gatewayUrl, loadWallet, onFunded]);
83
+ // Stripe-hosted Checkout (setup) in a popup — no Stripe Elements here.
84
+ const addPaymentMethod = useCallback(async () => {
85
+ setBusy(true);
86
+ setActionError(null);
87
+ try {
88
+ const headers = await authHeaders();
89
+ const res = await fetch(`${gatewayUrl}/api/payments/stripe/checkout-setup`, {
90
+ method: 'POST',
91
+ headers,
92
+ body: JSON.stringify({}),
93
+ });
94
+ const body = await res.json().catch(() => ({}));
95
+ if (!res.ok || !body?.url) {
96
+ throw new Error(body?.error || `Could not start checkout (${res.status})`);
97
+ }
98
+ const popup = window.open(body.url, 'hypery-add-card', 'width=480,height=720');
99
+ const added = await new Promise((resolve) => {
100
+ let settled = false;
101
+ const finish = (ok) => {
102
+ if (settled)
103
+ return;
104
+ settled = true;
105
+ window.removeEventListener('message', onMessage);
106
+ clearInterval(poll);
107
+ resolve(ok);
108
+ };
109
+ // Only trust the completion message from the gateway's own origin.
110
+ const expectedOrigin = (() => {
111
+ try {
112
+ return new URL(gatewayUrl).origin;
113
+ }
114
+ catch {
115
+ return typeof window !== 'undefined' ? window.location.origin : '';
116
+ }
117
+ })();
118
+ const onMessage = (event) => {
119
+ if (event.origin !== expectedOrigin)
120
+ return;
121
+ if (event?.data?.type === 'hypery:payment-method') {
122
+ finish(event.data.status === 'added');
123
+ }
124
+ };
125
+ window.addEventListener('message', onMessage);
126
+ const poll = setInterval(() => {
127
+ if (popup && popup.closed)
128
+ finish(false);
129
+ }, 700);
130
+ });
131
+ await loadWallet();
132
+ if (added)
133
+ onFunded?.();
134
+ }
135
+ catch (err) {
136
+ setActionError(err instanceof Error ? err.message : 'Could not add payment method');
137
+ }
138
+ finally {
139
+ setBusy(false);
140
+ }
141
+ }, [authHeaders, gatewayUrl, loadWallet, onFunded]);
142
+ if (!error)
143
+ return null;
144
+ const openSettings = (path) => {
145
+ if (!path)
146
+ return;
147
+ window.open(`${gatewayUrl}${path}`, '_blank');
148
+ };
149
+ const isSpendingLimit = error.code === 'SPENDING_LIMIT_EXCEEDED';
150
+ const isPaymentDeclined = error.code === 'PAYMENT_DECLINED';
151
+ const isPaymentMethodRequired = error.code === 'PAYMENT_METHOD_REQUIRED';
152
+ const title = funded
153
+ ? 'You’re all set'
154
+ : isPaymentMethodRequired
155
+ ? 'Add a payment method'
156
+ : isPaymentDeclined
157
+ ? 'Payment failed'
158
+ : isSpendingLimit
159
+ ? 'Spending limit reached'
160
+ : 'Add funds';
161
+ const mode = wallet?.mode;
162
+ const hasCard = !!wallet?.paymentMethod?.exists;
163
+ return (_jsx("div", { className: `fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm ${overlayClassName}`, children: _jsxs("div", { className: `relative w-full max-w-md bg-gray-900 border border-gray-700 rounded-lg shadow-2xl ${className}`, children: [_jsx("button", { onClick: onClose, className: "absolute top-4 right-4 text-gray-400 hover:text-gray-100 transition-colors z-10", "aria-label": "Close", children: _jsx("svg", { className: "w-6 h-6", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) }), _jsxs("div", { className: "p-6", children: [_jsx("h2", { className: "text-2xl font-bold text-center mb-4 text-white", children: title }), isLoading && (_jsxs("div", { className: "flex items-center justify-center py-8", children: [_jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500" }), _jsx("span", { className: "ml-3 text-gray-400", children: "Loading your account\u2026" })] })), !isLoading && (_jsxs(_Fragment, { children: [wallet && (_jsxs("div", { className: "bg-gray-800 border border-gray-700 rounded-lg p-4 mb-4", children: [_jsxs("div", { className: "flex justify-between items-center", children: [_jsx("span", { className: "text-sm text-gray-400", children: "Available credits" }), _jsx("span", { className: "text-2xl font-bold text-white", children: wallet.balance.current })] }), _jsxs("div", { className: "flex justify-between items-center mt-1", children: [_jsx("span", { className: "text-xs text-gray-500", children: mode === 'metered' ? 'Pay-as-you-go billing' : 'Prepaid balance' }), wallet.paymentMethod.exists && (_jsxs("span", { className: "text-xs text-gray-500", children: [wallet.paymentMethod.brand, " \u2022\u2022\u2022\u2022 ", wallet.paymentMethod.last4] }))] })] })), fetchError && (_jsxs("p", { className: "text-sm text-red-400 mb-4", children: ["\u26A0\uFE0F ", fetchError] })), actionError && (_jsxs("p", { className: "text-sm text-red-400 mb-4", children: ["\u26A0\uFE0F ", actionError] })), !funded && (_jsx("p", { className: "text-sm text-gray-400 text-center mb-5", children: error.message })), _jsxs("div", { className: "space-y-3", children: [funded ? (_jsx("button", { onClick: () => {
164
+ onRetry?.();
165
+ onClose();
166
+ }, className: "w-full px-4 py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-lg transition-colors font-medium", children: "Continue" })) : isSpendingLimit ? (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => openSettings(wallet?.settingsUrls.billing), className: "w-full px-4 py-3 bg-gray-800 hover:bg-gray-700 text-white border border-gray-600 rounded-lg transition-colors", children: "Manage spending limits" }), onRetry && (_jsx("button", { onClick: onRetry, className: "w-full px-4 py-3 bg-gray-800 hover:bg-gray-700 text-white border border-gray-600 rounded-lg transition-colors", children: "Try again" }))] })) : mode === 'metered' || !hasCard || isPaymentMethodRequired || isPaymentDeclined ? (_jsxs(_Fragment, { children: [_jsx("button", { onClick: addPaymentMethod, disabled: busy, className: "w-full px-4 py-3 bg-blue-600 hover:bg-blue-500 disabled:opacity-60 text-white rounded-lg transition-colors font-medium", children: busy ? 'Opening secure checkout…' : hasCard ? 'Update payment method' : 'Add a payment method' }), mode === 'metered' && (_jsx("p", { className: "text-xs text-gray-500 text-center", children: "You\u2019ll be billed automatically as you use the service." })), onRetry && (_jsx("button", { onClick: onRetry, className: "w-full px-4 py-2 text-gray-400 hover:text-white text-sm transition-colors", children: "Try again" }))] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "grid grid-cols-3 gap-2", children: QUICK_AMOUNTS.map((usd) => (_jsxs("button", { onClick: () => addFunds(usd), disabled: busy, className: "px-3 py-3 bg-blue-600 hover:bg-blue-500 disabled:opacity-60 text-white rounded-lg transition-colors font-medium", children: ["$", usd] }, usd))) }), _jsx("button", { onClick: () => openSettings(wallet?.settingsUrls.topup), className: "w-full px-4 py-2 text-gray-400 hover:text-white text-sm transition-colors", children: "Other amount\u2026" })] })), !funded && (_jsx("button", { onClick: onClose, className: "w-full px-4 py-2 text-gray-500 hover:text-gray-300 text-sm transition-colors", children: "Close" }))] })] }))] })] }) }));
167
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Sign In Component
3
+ * Similar to Clerk's <SignIn />
4
+ */
5
+ import React from 'react';
6
+ export interface SignInProps {
7
+ /** Button text */
8
+ buttonText?: string;
9
+ /** Custom className for styling */
10
+ className?: string;
11
+ /** Button style variant */
12
+ variant?: 'primary' | 'secondary' | 'outline';
13
+ /** Redirect path after sign in */
14
+ redirectTo?: string;
15
+ /** Show loading state */
16
+ loading?: boolean;
17
+ }
18
+ /**
19
+ * Pre-built sign-in button component
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * <SignIn buttonText="Sign in with Hypery" />
24
+ * ```
25
+ */
26
+ export declare function SignIn({ buttonText, className, variant, redirectTo, loading, }: SignInProps): React.JSX.Element;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Sign In Component
3
+ * Similar to Clerk's <SignIn />
4
+ */
5
+ 'use client';
6
+ import { jsx as _jsx } from "react/jsx-runtime";
7
+ import { useHyperyAuth } from '../lib/context';
8
+ /**
9
+ * Pre-built sign-in button component
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * <SignIn buttonText="Sign in with Hypery" />
14
+ * ```
15
+ */
16
+ export function SignIn({ buttonText = 'Sign in with Hypery', className, variant = 'primary', redirectTo, loading, }) {
17
+ const { login, isLoading } = useHyperyAuth();
18
+ const handleClick = () => {
19
+ if (redirectTo) {
20
+ sessionStorage.setItem('hypery_redirect_after_login', redirectTo);
21
+ }
22
+ login();
23
+ };
24
+ const isDisabled = isLoading || loading;
25
+ // Default styles based on variant
26
+ const getVariantStyles = () => {
27
+ switch (variant) {
28
+ case 'secondary':
29
+ return 'bg-gray-600 hover:bg-gray-700 text-white';
30
+ case 'outline':
31
+ return 'border-2 border-blue-600 text-blue-600 hover:bg-blue-50';
32
+ default:
33
+ return 'bg-blue-600 hover:bg-blue-700 text-white';
34
+ }
35
+ };
36
+ const defaultStyles = `
37
+ inline-flex items-center justify-center
38
+ px-6 py-2.5
39
+ rounded-lg
40
+ font-medium
41
+ transition-colors
42
+ focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
43
+ disabled:opacity-50 disabled:cursor-not-allowed
44
+ ${getVariantStyles()}
45
+ `.replace(/\s+/g, ' ').trim();
46
+ return (_jsx("button", { onClick: handleClick, disabled: isDisabled, className: className || defaultStyles, type: "button", children: isDisabled ? 'Loading...' : buttonText }));
47
+ }