@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.
- package/LICENSE +13 -0
- package/README.md +391 -0
- package/dist/components/AuthButton.d.ts +51 -0
- package/dist/components/AuthButton.js +60 -0
- package/dist/components/AuthModal.d.ts +20 -0
- package/dist/components/AuthModal.js +62 -0
- package/dist/components/BuyButton.d.ts +47 -0
- package/dist/components/BuyButton.js +74 -0
- package/dist/components/ErrorBoundary.d.ts +13 -0
- package/dist/components/ErrorBoundary.js +24 -0
- package/dist/components/HyperyModals.d.ts +30 -0
- package/dist/components/HyperyModals.js +30 -0
- package/dist/components/InsufficientCreditsAlert.d.ts +11 -0
- package/dist/components/InsufficientCreditsAlert.js +12 -0
- package/dist/components/ModernAuthForm.d.ts +52 -0
- package/dist/components/ModernAuthForm.js +83 -0
- package/dist/components/RestrictionModal.d.ts +43 -0
- package/dist/components/RestrictionModal.js +167 -0
- package/dist/components/SignIn.d.ts +26 -0
- package/dist/components/SignIn.js +47 -0
- package/dist/components/SignInForm.d.ts +41 -0
- package/dist/components/SignInForm.js +86 -0
- package/dist/components/SignUp.d.ts +26 -0
- package/dist/components/SignUp.js +52 -0
- package/dist/components/SpendingLimitAlert.d.ts +12 -0
- package/dist/components/SpendingLimitAlert.js +14 -0
- package/dist/components/UserButton.d.ts +26 -0
- package/dist/components/UserButton.js +35 -0
- package/dist/components/UserProfile.d.ts +22 -0
- package/dist/components/UserProfile.js +26 -0
- package/dist/components/WorkspaceSwitcher.d.ts +29 -0
- package/dist/components/WorkspaceSwitcher.js +66 -0
- package/dist/components/control.d.ts +64 -0
- package/dist/components/control.js +89 -0
- package/dist/components/ui/dialog.d.ts +19 -0
- package/dist/components/ui/dialog.js +53 -0
- package/dist/hooks/index.d.ts +22 -0
- package/dist/hooks/index.js +23 -0
- package/dist/hooks/useBuyerWallet.d.ts +37 -0
- package/dist/hooks/useBuyerWallet.js +66 -0
- package/dist/hooks/useCheckout.d.ts +27 -0
- package/dist/hooks/useCheckout.js +246 -0
- package/dist/hooks/useError.d.ts +19 -0
- package/dist/hooks/useError.js +36 -0
- package/dist/hooks/useMarketplace.d.ts +50 -0
- package/dist/hooks/useMarketplace.js +69 -0
- package/dist/hooks/useMemberships.d.ts +71 -0
- package/dist/hooks/useMemberships.js +138 -0
- package/dist/hooks/useWallet.d.ts +62 -0
- package/dist/hooks/useWallet.js +123 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +52 -0
- package/dist/lib/context.d.ts +50 -0
- package/dist/lib/context.js +409 -0
- package/dist/lib/oauth.d.ts +49 -0
- package/dist/lib/oauth.js +149 -0
- package/dist/lib/parse-error.d.ts +45 -0
- package/dist/lib/parse-error.js +185 -0
- package/dist/lib/parse-stream.d.ts +43 -0
- package/dist/lib/parse-stream.js +89 -0
- package/dist/lib/popup.d.ts +42 -0
- package/dist/lib/popup.js +80 -0
- package/dist/lib/storage.d.ts +43 -0
- package/dist/lib/storage.js +125 -0
- package/dist/lib/utils.d.ts +8 -0
- package/dist/lib/utils.js +11 -0
- package/dist/types/index.d.ts +191 -0
- package/dist/types/index.js +4 -0
- package/package.json +78 -0
- package/src/components/AuthButton.tsx +123 -0
- package/src/components/AuthModal.tsx +240 -0
- package/src/components/BuyButton.tsx +150 -0
- package/src/components/ErrorBoundary.tsx +97 -0
- package/src/components/HyperyModals.tsx +83 -0
- package/src/components/InsufficientCreditsAlert.tsx +73 -0
- package/src/components/ModernAuthForm.tsx +322 -0
- package/src/components/RestrictionModal.tsx +373 -0
- package/src/components/SignIn.tsx +84 -0
- package/src/components/SignInForm.tsx +256 -0
- package/src/components/SignUp.tsx +86 -0
- package/src/components/SpendingLimitAlert.tsx +91 -0
- package/src/components/UserButton.tsx +106 -0
- package/src/components/UserProfile.tsx +106 -0
- package/src/components/WorkspaceSwitcher.tsx +199 -0
- package/src/components/control.tsx +133 -0
- package/src/components/ui/dialog.tsx +151 -0
- package/src/hooks/index.ts +65 -0
- package/src/hooks/useBuyerWallet.ts +117 -0
- package/src/hooks/useCheckout.ts +312 -0
- package/src/hooks/useError.ts +60 -0
- package/src/hooks/useMarketplace.ts +129 -0
- package/src/hooks/useMemberships.ts +203 -0
- package/src/hooks/useWallet.ts +170 -0
- package/src/index.ts +147 -0
- package/src/lib/context.tsx +478 -0
- package/src/lib/oauth.ts +215 -0
- package/src/lib/parse-error.ts +224 -0
- package/src/lib/parse-stream.ts +121 -0
- package/src/lib/popup.ts +98 -0
- package/src/lib/storage.ts +140 -0
- package/src/lib/utils.ts +14 -0
- package/src/types/index.ts +216 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UserProfile Component
|
|
3
|
+
* Display user profile information in a card format
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use client';
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { useUser } from '../lib/context';
|
|
10
|
+
|
|
11
|
+
export interface UserProfileProps {
|
|
12
|
+
/** Show extended profile information */
|
|
13
|
+
showExtended?: boolean;
|
|
14
|
+
/** Custom CSS class */
|
|
15
|
+
className?: string;
|
|
16
|
+
/** Show loading state */
|
|
17
|
+
showLoading?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* User profile card component
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* <UserProfile showExtended />
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function UserProfile({
|
|
29
|
+
showExtended = true,
|
|
30
|
+
className,
|
|
31
|
+
showLoading = true,
|
|
32
|
+
}: UserProfileProps) {
|
|
33
|
+
const { user, isLoading } = useUser();
|
|
34
|
+
|
|
35
|
+
if (isLoading && showLoading) {
|
|
36
|
+
return (
|
|
37
|
+
<div className={className || 'bg-white rounded-lg shadow p-6'}>
|
|
38
|
+
<div className="animate-pulse">
|
|
39
|
+
<div className="flex items-center space-x-4">
|
|
40
|
+
<div className="w-16 h-16 bg-gray-200 rounded-full" />
|
|
41
|
+
<div className="flex-1 space-y-2">
|
|
42
|
+
<div className="h-4 bg-gray-200 rounded w-3/4" />
|
|
43
|
+
<div className="h-3 bg-gray-200 rounded w-1/2" />
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!user) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const defaultStyles = 'bg-white rounded-lg shadow p-6';
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div className={className || defaultStyles}>
|
|
59
|
+
<div className="flex items-start space-x-4">
|
|
60
|
+
{/* Avatar */}
|
|
61
|
+
{user.image ? (
|
|
62
|
+
<img
|
|
63
|
+
src={user.image}
|
|
64
|
+
alt={user.name}
|
|
65
|
+
className="w-16 h-16 rounded-full object-cover"
|
|
66
|
+
/>
|
|
67
|
+
) : (
|
|
68
|
+
<div className="w-16 h-16 bg-gradient-to-br from-blue-600 to-purple-600 rounded-full flex items-center justify-center text-white text-2xl font-bold">
|
|
69
|
+
{user.name.charAt(0).toUpperCase()}
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
72
|
+
|
|
73
|
+
{/* User Info */}
|
|
74
|
+
<div className="flex-1 min-w-0">
|
|
75
|
+
<h3 className="text-xl font-semibold text-gray-900 truncate">
|
|
76
|
+
{user.name}
|
|
77
|
+
</h3>
|
|
78
|
+
<p className="text-sm text-gray-600 truncate">{user.email}</p>
|
|
79
|
+
|
|
80
|
+
{showExtended && (
|
|
81
|
+
<div className="mt-4 space-y-2">
|
|
82
|
+
<div className="flex items-center justify-between text-sm">
|
|
83
|
+
<span className="text-gray-500">User ID</span>
|
|
84
|
+
<code className="text-xs bg-gray-100 px-2 py-1 rounded font-mono">
|
|
85
|
+
{user.id}
|
|
86
|
+
</code>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
{showExtended && (
|
|
94
|
+
<div className="mt-6 pt-6 border-t border-gray-200">
|
|
95
|
+
<div className="flex items-center justify-between">
|
|
96
|
+
<span className="text-sm text-gray-500">Profile Status</span>
|
|
97
|
+
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
|
98
|
+
Active
|
|
99
|
+
</span>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <WorkspaceSwitcher /> — drop-in dropdown that lists every workspace the
|
|
3
|
+
* user belongs to (across all orgs/teams) and switches the active one.
|
|
4
|
+
*
|
|
5
|
+
* Designed for the top-bar of consumer apps. Headless-ish: ships with
|
|
6
|
+
* sensible default styles via tailwind utility classes but they can be
|
|
7
|
+
* fully overridden via `className`.
|
|
8
|
+
*
|
|
9
|
+
* Example:
|
|
10
|
+
* <WorkspaceSwitcher
|
|
11
|
+
* onSwitched={(teamId, workspaceId) => router.refresh()}
|
|
12
|
+
* />
|
|
13
|
+
*
|
|
14
|
+
* The component groups workspaces by team. Personal team is labelled
|
|
15
|
+
* "Personal" rather than the literal "{Name}'s Workspace" team name to
|
|
16
|
+
* keep the switcher compact.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use client';
|
|
20
|
+
|
|
21
|
+
import React, { useCallback, useState } from 'react';
|
|
22
|
+
import { ChevronsUpDown, Check, Building2, User as UserIcon } from 'lucide-react';
|
|
23
|
+
import { useHyperyAuth } from '../lib/context';
|
|
24
|
+
import {
|
|
25
|
+
useMemberships,
|
|
26
|
+
setActiveWorkspace,
|
|
27
|
+
type MembershipEntry,
|
|
28
|
+
type MembershipWorkspace,
|
|
29
|
+
} from '../hooks/useMemberships';
|
|
30
|
+
|
|
31
|
+
export interface WorkspaceSwitcherProps {
|
|
32
|
+
/** Called after a successful switch — typically `router.refresh()`. */
|
|
33
|
+
onSwitched?: (teamId: string, workspaceId: string) => void;
|
|
34
|
+
/** Override the Hypery base URL (defaults to NEXT_PUBLIC_GATEWAY_URL). */
|
|
35
|
+
gatewayUrl?: string;
|
|
36
|
+
/** Extra classes appended to the trigger button. */
|
|
37
|
+
className?: string;
|
|
38
|
+
/** Optional aria-label for the trigger. */
|
|
39
|
+
ariaLabel?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function WorkspaceSwitcher({
|
|
43
|
+
onSwitched,
|
|
44
|
+
gatewayUrl,
|
|
45
|
+
className = '',
|
|
46
|
+
ariaLabel = 'Switch workspace',
|
|
47
|
+
}: WorkspaceSwitcherProps) {
|
|
48
|
+
const { getAccessToken } = useHyperyAuth();
|
|
49
|
+
const { data, isLoading, reload } = useMemberships();
|
|
50
|
+
const [open, setOpen] = useState(false);
|
|
51
|
+
const [switching, setSwitching] = useState<string | null>(null);
|
|
52
|
+
|
|
53
|
+
const activeWorkspaceId = data?.activeWorkspaceId ?? null;
|
|
54
|
+
|
|
55
|
+
// Resolve the currently-active entry for the trigger label.
|
|
56
|
+
const flat: Array<{
|
|
57
|
+
membership: MembershipEntry;
|
|
58
|
+
workspace: MembershipWorkspace;
|
|
59
|
+
}> =
|
|
60
|
+
data?.memberships.flatMap((m) =>
|
|
61
|
+
m.workspaces.map((w) => ({ membership: m, workspace: w })),
|
|
62
|
+
) ?? [];
|
|
63
|
+
const activeEntry =
|
|
64
|
+
flat.find((e) => e.workspace.id === activeWorkspaceId) ?? flat[0];
|
|
65
|
+
|
|
66
|
+
const handleSwitch = useCallback(
|
|
67
|
+
async (teamId: string, workspaceId: string) => {
|
|
68
|
+
setSwitching(workspaceId);
|
|
69
|
+
try {
|
|
70
|
+
await setActiveWorkspace({
|
|
71
|
+
teamId,
|
|
72
|
+
workspaceId,
|
|
73
|
+
getAccessToken,
|
|
74
|
+
gatewayUrl,
|
|
75
|
+
});
|
|
76
|
+
await reload();
|
|
77
|
+
setOpen(false);
|
|
78
|
+
onSwitched?.(teamId, workspaceId);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error('[WorkspaceSwitcher] switch failed:', err);
|
|
81
|
+
} finally {
|
|
82
|
+
setSwitching(null);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
[getAccessToken, gatewayUrl, onSwitched, reload],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
if (isLoading && !data) {
|
|
89
|
+
return (
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
disabled
|
|
93
|
+
className={`inline-flex items-center gap-2 rounded-md border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 px-3 py-1.5 text-sm text-zinc-500 ${className}`}
|
|
94
|
+
>
|
|
95
|
+
<span className="size-4 animate-pulse rounded-sm bg-zinc-300 dark:bg-zinc-700" />
|
|
96
|
+
Loading workspaces…
|
|
97
|
+
</button>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!data || data.memberships.length === 0) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className="relative inline-block text-left">
|
|
107
|
+
<button
|
|
108
|
+
type="button"
|
|
109
|
+
onClick={() => setOpen((v) => !v)}
|
|
110
|
+
aria-label={ariaLabel}
|
|
111
|
+
aria-haspopup="listbox"
|
|
112
|
+
aria-expanded={open}
|
|
113
|
+
className={`inline-flex items-center gap-2 rounded-md border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 px-3 py-1.5 text-sm font-medium text-zinc-900 dark:text-zinc-100 hover:bg-zinc-50 dark:hover:bg-zinc-800 focus:outline-none focus:ring-2 focus:ring-blue-500 ${className}`}
|
|
114
|
+
>
|
|
115
|
+
{activeEntry?.membership.team.isPersonal ? (
|
|
116
|
+
<UserIcon className="h-4 w-4 text-zinc-500" aria-hidden />
|
|
117
|
+
) : (
|
|
118
|
+
<Building2 className="h-4 w-4 text-zinc-500" aria-hidden />
|
|
119
|
+
)}
|
|
120
|
+
<span className="truncate max-w-[180px]">
|
|
121
|
+
{activeEntry ? (
|
|
122
|
+
<>
|
|
123
|
+
<span className="text-zinc-500">
|
|
124
|
+
{activeEntry.membership.team.isPersonal
|
|
125
|
+
? 'Personal'
|
|
126
|
+
: activeEntry.membership.team.name}
|
|
127
|
+
</span>
|
|
128
|
+
<span className="mx-1.5 text-zinc-300 dark:text-zinc-600">·</span>
|
|
129
|
+
<span>{activeEntry.workspace.name}</span>
|
|
130
|
+
</>
|
|
131
|
+
) : (
|
|
132
|
+
'Select workspace'
|
|
133
|
+
)}
|
|
134
|
+
</span>
|
|
135
|
+
<ChevronsUpDown className="h-3.5 w-3.5 text-zinc-400" aria-hidden />
|
|
136
|
+
</button>
|
|
137
|
+
|
|
138
|
+
{open && (
|
|
139
|
+
<>
|
|
140
|
+
{/* Click-away */}
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
aria-hidden
|
|
144
|
+
tabIndex={-1}
|
|
145
|
+
onClick={() => setOpen(false)}
|
|
146
|
+
className="fixed inset-0 z-40 cursor-default"
|
|
147
|
+
/>
|
|
148
|
+
<div
|
|
149
|
+
role="listbox"
|
|
150
|
+
className="absolute z-50 mt-1 max-h-[28rem] w-72 overflow-y-auto rounded-md border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 shadow-lg p-1"
|
|
151
|
+
>
|
|
152
|
+
{data.memberships.map((m) => (
|
|
153
|
+
<div key={m.team.id} className="py-1">
|
|
154
|
+
<div className="px-2 pt-1 pb-1 text-[10px] font-semibold uppercase tracking-wider text-zinc-500 flex items-center gap-1.5">
|
|
155
|
+
{m.team.isPersonal ? (
|
|
156
|
+
<UserIcon className="h-3 w-3" />
|
|
157
|
+
) : (
|
|
158
|
+
<Building2 className="h-3 w-3" />
|
|
159
|
+
)}
|
|
160
|
+
{m.team.isPersonal ? 'Personal' : m.team.name}
|
|
161
|
+
</div>
|
|
162
|
+
{m.workspaces.map((w) => {
|
|
163
|
+
const isActive = w.id === activeWorkspaceId;
|
|
164
|
+
const isSwitching = switching === w.id;
|
|
165
|
+
return (
|
|
166
|
+
<button
|
|
167
|
+
key={w.id}
|
|
168
|
+
type="button"
|
|
169
|
+
role="option"
|
|
170
|
+
aria-selected={isActive}
|
|
171
|
+
disabled={isSwitching}
|
|
172
|
+
onClick={() => handleSwitch(m.team.id, w.id)}
|
|
173
|
+
className={`flex w-full items-center justify-between gap-2 rounded-sm px-2 py-1.5 text-sm text-left hover:bg-zinc-100 dark:hover:bg-zinc-800 ${
|
|
174
|
+
isActive ? 'bg-zinc-50 dark:bg-zinc-800/60' : ''
|
|
175
|
+
} disabled:opacity-50`}
|
|
176
|
+
>
|
|
177
|
+
<span className="flex items-center gap-1.5 truncate">
|
|
178
|
+
{w.icon && <span aria-hidden>{w.icon}</span>}
|
|
179
|
+
<span className="truncate">{w.name}</span>
|
|
180
|
+
{w.isDefault && (
|
|
181
|
+
<span className="ml-1 text-[9px] uppercase tracking-wider text-zinc-400">
|
|
182
|
+
default
|
|
183
|
+
</span>
|
|
184
|
+
)}
|
|
185
|
+
</span>
|
|
186
|
+
{isActive && (
|
|
187
|
+
<Check className="h-3.5 w-3.5 text-blue-600" />
|
|
188
|
+
)}
|
|
189
|
+
</button>
|
|
190
|
+
);
|
|
191
|
+
})}
|
|
192
|
+
</div>
|
|
193
|
+
))}
|
|
194
|
+
</div>
|
|
195
|
+
</>
|
|
196
|
+
)}
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control Components
|
|
3
|
+
* Similar to Clerk's <SignedIn />, <SignedOut />, etc.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use client';
|
|
7
|
+
|
|
8
|
+
import type { ReactNode } from 'react';
|
|
9
|
+
import { useHyperyAuth } from '../lib/context';
|
|
10
|
+
|
|
11
|
+
export interface SignedInProps {
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
/** Fallback content to show while loading */
|
|
14
|
+
fallback?: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Only renders children when user is authenticated
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* <SignedIn>
|
|
23
|
+
* <Dashboard />
|
|
24
|
+
* </SignedIn>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function SignedIn({ children, fallback }: SignedInProps) {
|
|
28
|
+
const { isAuthenticated, isLoading } = useHyperyAuth();
|
|
29
|
+
|
|
30
|
+
if (isLoading) {
|
|
31
|
+
return fallback || null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return isAuthenticated ? children : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SignedOutProps {
|
|
38
|
+
children: ReactNode;
|
|
39
|
+
/** Fallback content to show while loading */
|
|
40
|
+
fallback?: ReactNode;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Only renders children when user is NOT authenticated
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* <SignedOut>
|
|
49
|
+
* <SignIn />
|
|
50
|
+
* </SignedOut>
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function SignedOut({ children, fallback }: SignedOutProps) {
|
|
54
|
+
const { isAuthenticated, isLoading } = useHyperyAuth();
|
|
55
|
+
|
|
56
|
+
if (isLoading) {
|
|
57
|
+
return fallback || null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return !isAuthenticated ? children : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Redirects to sign in when user is not authenticated
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```tsx
|
|
68
|
+
* <RedirectToSignIn />
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function RedirectToSignIn() {
|
|
72
|
+
const { login, isLoading, isAuthenticated } = useHyperyAuth();
|
|
73
|
+
|
|
74
|
+
if (!isLoading && !isAuthenticated) {
|
|
75
|
+
login();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ProtectProps {
|
|
82
|
+
children: ReactNode;
|
|
83
|
+
/** Fallback to show when not authenticated */
|
|
84
|
+
fallback?: ReactNode;
|
|
85
|
+
/** Custom redirect logic */
|
|
86
|
+
onUnauthenticated?: () => void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Protects content - redirects to sign in if not authenticated
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```tsx
|
|
94
|
+
* <Protect fallback={<SignIn />}>
|
|
95
|
+
* <ProtectedContent />
|
|
96
|
+
* </Protect>
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function Protect({
|
|
100
|
+
children,
|
|
101
|
+
fallback,
|
|
102
|
+
onUnauthenticated,
|
|
103
|
+
}: ProtectProps) {
|
|
104
|
+
const { isAuthenticated, isLoading, login, isLoggingOut } = useHyperyAuth();
|
|
105
|
+
|
|
106
|
+
if (isLoading) {
|
|
107
|
+
return fallback || null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Don't trigger login if we're in the middle of logging out
|
|
111
|
+
if (!isAuthenticated && !isLoggingOut) {
|
|
112
|
+
if (onUnauthenticated) {
|
|
113
|
+
onUnauthenticated();
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (fallback) {
|
|
118
|
+
return fallback;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Default: initiate login
|
|
122
|
+
login();
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// If logging out, show fallback or nothing
|
|
127
|
+
if (isLoggingOut) {
|
|
128
|
+
return fallback || null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return children;
|
|
132
|
+
}
|
|
133
|
+
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
|
5
|
+
import { X } from "lucide-react"
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils"
|
|
8
|
+
|
|
9
|
+
const Dialog = DialogPrimitive.Root
|
|
10
|
+
|
|
11
|
+
const DialogTrigger = DialogPrimitive.Trigger
|
|
12
|
+
|
|
13
|
+
const DialogPortal = DialogPrimitive.Portal
|
|
14
|
+
|
|
15
|
+
const DialogClose = DialogPrimitive.Close
|
|
16
|
+
|
|
17
|
+
const DialogOverlay = React.forwardRef<
|
|
18
|
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
19
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
20
|
+
>(({ className, style, ...props }, ref) => (
|
|
21
|
+
<DialogPrimitive.Overlay
|
|
22
|
+
ref={ref}
|
|
23
|
+
style={{
|
|
24
|
+
position: 'fixed',
|
|
25
|
+
inset: 0,
|
|
26
|
+
zIndex: 50,
|
|
27
|
+
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
|
28
|
+
...style,
|
|
29
|
+
}}
|
|
30
|
+
className={className}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
))
|
|
34
|
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
|
35
|
+
|
|
36
|
+
const DialogContent = React.forwardRef<
|
|
37
|
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
38
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
39
|
+
>(({ className, children, style, ...props }, ref) => (
|
|
40
|
+
<DialogPortal>
|
|
41
|
+
<DialogOverlay />
|
|
42
|
+
<DialogPrimitive.Content
|
|
43
|
+
ref={ref}
|
|
44
|
+
style={{
|
|
45
|
+
position: 'fixed',
|
|
46
|
+
left: '50%',
|
|
47
|
+
top: '50%',
|
|
48
|
+
zIndex: 50,
|
|
49
|
+
display: 'grid',
|
|
50
|
+
width: '100%',
|
|
51
|
+
maxWidth: '32rem',
|
|
52
|
+
transform: 'translate(-50%, -50%)',
|
|
53
|
+
gap: '1rem',
|
|
54
|
+
border: '1px solid #e5e7eb',
|
|
55
|
+
backgroundColor: '#ffffff',
|
|
56
|
+
padding: '1.5rem',
|
|
57
|
+
boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
|
|
58
|
+
borderRadius: '0.5rem',
|
|
59
|
+
...style,
|
|
60
|
+
}}
|
|
61
|
+
className={className}
|
|
62
|
+
{...props}
|
|
63
|
+
>
|
|
64
|
+
{children}
|
|
65
|
+
<DialogPrimitive.Close
|
|
66
|
+
style={{
|
|
67
|
+
position: 'absolute',
|
|
68
|
+
right: '1rem',
|
|
69
|
+
top: '1rem',
|
|
70
|
+
opacity: 0.7,
|
|
71
|
+
cursor: 'pointer',
|
|
72
|
+
border: 'none',
|
|
73
|
+
background: 'transparent',
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
<X style={{ width: '1rem', height: '1rem' }} />
|
|
77
|
+
<span style={{ position: 'absolute', width: '1px', height: '1px', padding: 0, margin: '-1px', overflow: 'hidden', clip: 'rect(0, 0, 0, 0)', whiteSpace: 'nowrap', borderWidth: 0 }}>Close</span>
|
|
78
|
+
</DialogPrimitive.Close>
|
|
79
|
+
</DialogPrimitive.Content>
|
|
80
|
+
</DialogPortal>
|
|
81
|
+
))
|
|
82
|
+
DialogContent.displayName = DialogPrimitive.Content.displayName
|
|
83
|
+
|
|
84
|
+
const DialogHeader = ({
|
|
85
|
+
className,
|
|
86
|
+
...props
|
|
87
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
88
|
+
<div
|
|
89
|
+
className={cn(
|
|
90
|
+
"flex flex-col space-y-1.5 text-center sm:text-left",
|
|
91
|
+
className
|
|
92
|
+
)}
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
95
|
+
)
|
|
96
|
+
DialogHeader.displayName = "DialogHeader"
|
|
97
|
+
|
|
98
|
+
const DialogFooter = ({
|
|
99
|
+
className,
|
|
100
|
+
...props
|
|
101
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
102
|
+
<div
|
|
103
|
+
className={cn(
|
|
104
|
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
|
105
|
+
className
|
|
106
|
+
)}
|
|
107
|
+
{...props}
|
|
108
|
+
/>
|
|
109
|
+
)
|
|
110
|
+
DialogFooter.displayName = "DialogFooter"
|
|
111
|
+
|
|
112
|
+
const DialogTitle = React.forwardRef<
|
|
113
|
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
114
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
115
|
+
>(({ className, ...props }, ref) => (
|
|
116
|
+
<DialogPrimitive.Title
|
|
117
|
+
ref={ref}
|
|
118
|
+
className={cn(
|
|
119
|
+
"text-lg font-semibold leading-none tracking-tight",
|
|
120
|
+
className
|
|
121
|
+
)}
|
|
122
|
+
{...props}
|
|
123
|
+
/>
|
|
124
|
+
))
|
|
125
|
+
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
|
126
|
+
|
|
127
|
+
const DialogDescription = React.forwardRef<
|
|
128
|
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
129
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
130
|
+
>(({ className, ...props }, ref) => (
|
|
131
|
+
<DialogPrimitive.Description
|
|
132
|
+
ref={ref}
|
|
133
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
134
|
+
{...props}
|
|
135
|
+
/>
|
|
136
|
+
))
|
|
137
|
+
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
|
138
|
+
|
|
139
|
+
export {
|
|
140
|
+
Dialog,
|
|
141
|
+
DialogPortal,
|
|
142
|
+
DialogOverlay,
|
|
143
|
+
DialogTrigger,
|
|
144
|
+
DialogClose,
|
|
145
|
+
DialogContent,
|
|
146
|
+
DialogHeader,
|
|
147
|
+
DialogFooter,
|
|
148
|
+
DialogTitle,
|
|
149
|
+
DialogDescription,
|
|
150
|
+
}
|
|
151
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export hooks for convenience
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { useHyperyAuth, useUser } from '../lib/context';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Alias for useHyperyAuth to match Clerk's API
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* const { isLoaded, isAuthenticated, user } = useAuth();
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export { useHyperyAuth as useAuth } from '../lib/context';
|
|
15
|
+
|
|
16
|
+
// Workspace + membership hooks for the WorkspaceSwitcher and consumer apps.
|
|
17
|
+
export {
|
|
18
|
+
useMemberships,
|
|
19
|
+
useActiveWorkspace,
|
|
20
|
+
setActiveWorkspace,
|
|
21
|
+
} from './useMemberships';
|
|
22
|
+
export type {
|
|
23
|
+
MembershipEntry,
|
|
24
|
+
MembershipTeam,
|
|
25
|
+
MembershipWorkspace,
|
|
26
|
+
MembershipsResponse,
|
|
27
|
+
ActiveWorkspace,
|
|
28
|
+
} from './useMemberships';
|
|
29
|
+
|
|
30
|
+
// Mode-aware wallet hook (balance, 1-click add funds, add payment method).
|
|
31
|
+
export { useWallet } from './useWallet';
|
|
32
|
+
export type {
|
|
33
|
+
BillingMode,
|
|
34
|
+
WalletState,
|
|
35
|
+
WalletTier,
|
|
36
|
+
UseWalletReturn,
|
|
37
|
+
} from './useWallet';
|
|
38
|
+
|
|
39
|
+
// "Buy with Hypery" marketplace layer (Stripe Connect, consumer side).
|
|
40
|
+
export { useBuyerWallet } from './useBuyerWallet';
|
|
41
|
+
export type {
|
|
42
|
+
BuyerPaymentMethod,
|
|
43
|
+
AddCardOptions,
|
|
44
|
+
UseBuyerWalletReturn,
|
|
45
|
+
} from './useBuyerWallet';
|
|
46
|
+
export { useMarketplace } from './useMarketplace';
|
|
47
|
+
export type {
|
|
48
|
+
BuyInput,
|
|
49
|
+
BuySuccess,
|
|
50
|
+
BuyFailure,
|
|
51
|
+
BuyResult,
|
|
52
|
+
UseMarketplaceReturn,
|
|
53
|
+
} from './useMarketplace';
|
|
54
|
+
|
|
55
|
+
// Seamless auth + charge orchestrator (login → charge → add-card → retry) in
|
|
56
|
+
// popup or redirect mode. Powers <BuyButton>; use directly for custom buttons
|
|
57
|
+
// and for AI-credit top-ups (kind: 'topup').
|
|
58
|
+
export { useCheckout } from './useCheckout';
|
|
59
|
+
export type {
|
|
60
|
+
CheckoutInput,
|
|
61
|
+
CheckoutStatus,
|
|
62
|
+
CheckoutResult,
|
|
63
|
+
UseCheckoutReturn,
|
|
64
|
+
} from './useCheckout';
|
|
65
|
+
|