@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
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Pay It Forward License (PIF)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The Hypery Project Authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
This license includes a perpetual, worldwide, non-exclusive, royalty-free, irrevocable (except as stated below) grant for patent claims necessarily infringed by the use of the Software or any contributions submitted to it; terminating automatically when an entity initiates patent litigation (including a cross-claim or counterclaim) alleging that the Software or any portion thereof infringes a patent.
|
|
10
|
+
|
|
11
|
+
The software is provided "as is," without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software.
|
|
12
|
+
|
|
13
|
+
Recipients of the Software should pay it forward by contributing to the open-source community through actions including but not limited to providing code, documentation, tutorials, guides, support, feedback, or promoting open-source projects through advocacy or related efforts that advance innovation and community growth.
|
package/README.md
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# @hyperyai/sdk
|
|
2
|
+
|
|
3
|
+
Drop-in authentication and error handling library for Hypery apps. Simple, secure, and built for React.
|
|
4
|
+
|
|
5
|
+
This package provides everything you need for OAuth authentication, user management, and structured error handling (spending limits, insufficient credits, etc.) in your Hypery applications.
|
|
6
|
+
|
|
7
|
+
Inspired by [Clerk](https://clerk.com/react-authentication), but purpose-built for Hypery's OAuth system.
|
|
8
|
+
|
|
9
|
+
## 📸 Component gallery
|
|
10
|
+
|
|
11
|
+
See **[docs/COMPONENTS.md](./docs/COMPONENTS.md)** for a visual reference of every component with live screenshots and usage.
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- 🔐 **Secure OAuth 2.0 + PKCE** - Industry-standard authentication
|
|
18
|
+
- ⚛️ **React-first** - Hooks and components that feel natural
|
|
19
|
+
- 🎨 **Customizable** - Bring your own UI or use our defaults
|
|
20
|
+
- 📦 **Tiny** - Minimal dependencies, maximum performance
|
|
21
|
+
- 🔄 **Auto-refresh** - Seamless token management
|
|
22
|
+
- 💾 **Flexible storage** - localStorage, sessionStorage, or memory
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @hyperyai/sdk
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The package ships compiled JavaScript with type declarations (plus the TypeScript
|
|
31
|
+
source under `src/` for reference), so it works out of the box — no
|
|
32
|
+
`transpilePackages` or build configuration needed.
|
|
33
|
+
|
|
34
|
+
## Local development against this repo
|
|
35
|
+
|
|
36
|
+
To develop `@hyperyai/sdk` alongside a consuming app, use
|
|
37
|
+
[yalc](https://github.com/wclr/yalc) (npm link duplicates React and breaks hooks):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# in hypery-sdk
|
|
41
|
+
npm i -g yalc
|
|
42
|
+
yalc publish # builds via prepack and stores the real pack output
|
|
43
|
+
# in your app
|
|
44
|
+
yalc add @hyperyai/sdk && npm install
|
|
45
|
+
|
|
46
|
+
# iterate: rebuild + push updates into consumers
|
|
47
|
+
yalc push # in hypery-sdk, after changes
|
|
48
|
+
|
|
49
|
+
# before committing your app
|
|
50
|
+
yalc remove --all && npm install
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
### 1. Wrap your app with `HyperyProvider`
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { HyperyProvider } from '@hyperyai/sdk';
|
|
59
|
+
|
|
60
|
+
function App() {
|
|
61
|
+
return (
|
|
62
|
+
<HyperyProvider
|
|
63
|
+
config={{
|
|
64
|
+
clientId: 'your-client-id',
|
|
65
|
+
redirectUri: 'http://localhost:3000/callback',
|
|
66
|
+
gatewayUrl: 'https://api.hypery.ai',
|
|
67
|
+
// Optional: defaults to ['read', 'write', 'ai:chat', 'ai:completions', 'ai:models', 'billing:read']
|
|
68
|
+
scopes: ['read', 'write', 'ai:chat', 'ai:completions', 'ai:models', 'billing:read'],
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<YourApp />
|
|
72
|
+
</HyperyProvider>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2. Use authentication components
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { SignedIn, SignedOut, SignIn, UserButton } from '@hyperyai/sdk';
|
|
81
|
+
|
|
82
|
+
function YourApp() {
|
|
83
|
+
return (
|
|
84
|
+
<>
|
|
85
|
+
<SignedIn>
|
|
86
|
+
<header>
|
|
87
|
+
<h1>Welcome!</h1>
|
|
88
|
+
<UserButton showUserInfo />
|
|
89
|
+
</header>
|
|
90
|
+
<Dashboard />
|
|
91
|
+
</SignedIn>
|
|
92
|
+
|
|
93
|
+
<SignedOut>
|
|
94
|
+
<div className="login-page">
|
|
95
|
+
<h1>Sign in to continue</h1>
|
|
96
|
+
<SignIn buttonText="Sign in with Hypery" />
|
|
97
|
+
</div>
|
|
98
|
+
</SignedOut>
|
|
99
|
+
</>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 3. Access user data with hooks
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import { useUser, useHyperyAuth } from '@hyperyai/sdk';
|
|
108
|
+
|
|
109
|
+
function Dashboard() {
|
|
110
|
+
const { user, isLoading } = useUser();
|
|
111
|
+
const { logout } = useHyperyAuth();
|
|
112
|
+
|
|
113
|
+
if (isLoading) return <div>Loading...</div>;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div>
|
|
117
|
+
<h1>Welcome, {user?.name}!</h1>
|
|
118
|
+
<p>Email: {user?.email}</p>
|
|
119
|
+
<button onClick={logout}>Sign out</button>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Components
|
|
126
|
+
|
|
127
|
+
### `<HyperyProvider />`
|
|
128
|
+
|
|
129
|
+
The main provider component. Wrap your app with this.
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
<HyperyProvider
|
|
133
|
+
config={{
|
|
134
|
+
clientId: string; // Your OAuth client ID
|
|
135
|
+
redirectUri: string; // Callback URL after auth
|
|
136
|
+
gatewayUrl: string; // Hypery API URL
|
|
137
|
+
scopes?: string[]; // OAuth scopes (default: ['read', 'write'])
|
|
138
|
+
storage?: 'localStorage' | 'sessionStorage' | 'memory'; // Storage type
|
|
139
|
+
}}
|
|
140
|
+
>
|
|
141
|
+
{children}
|
|
142
|
+
</HyperyProvider>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Authentication Components
|
|
146
|
+
|
|
147
|
+
**`<SignIn />`** - Sign-in button
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
<SignIn
|
|
151
|
+
buttonText="Sign in with Hypery"
|
|
152
|
+
variant="primary" // 'primary' | 'secondary' | 'outline'
|
|
153
|
+
className="custom-button-class"
|
|
154
|
+
/>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**`<SignUp />`** - Sign-up button
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
<SignUp
|
|
161
|
+
buttonText="Get Started"
|
|
162
|
+
variant="primary"
|
|
163
|
+
onSignUpStart={() => console.log('Starting signup')}
|
|
164
|
+
/>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**`<SignInForm />`** - Embedded login form with social OAuth
|
|
168
|
+
|
|
169
|
+
Full-featured login form with GitHub, Google, and email/password authentication.
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
<SignInForm
|
|
173
|
+
showCard
|
|
174
|
+
showTitle
|
|
175
|
+
showSocial // Show GitHub and Google buttons
|
|
176
|
+
showEmailPassword // Show email/password form
|
|
177
|
+
title="Welcome back"
|
|
178
|
+
description="Sign in to your account"
|
|
179
|
+
onSuccess={() => console.log('Logged in!')}
|
|
180
|
+
onError={(error) => console.error(error)}
|
|
181
|
+
/>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
// Social auth only (no email/password)
|
|
186
|
+
<SignInForm
|
|
187
|
+
showCard
|
|
188
|
+
showSocial={true}
|
|
189
|
+
showEmailPassword={false}
|
|
190
|
+
/>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**`<UserButton />`** - User avatar with dropdown menu
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
<UserButton
|
|
197
|
+
showUserInfo
|
|
198
|
+
size="md" // 'sm' | 'md' | 'lg'
|
|
199
|
+
renderDropdown={(user, logout) => (
|
|
200
|
+
<div>
|
|
201
|
+
<p>{user.name}</p>
|
|
202
|
+
<button onClick={logout}>Sign out</button>
|
|
203
|
+
</div>
|
|
204
|
+
)}
|
|
205
|
+
/>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**`<UserProfile />`** - User profile card
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
<UserProfile
|
|
212
|
+
showExtended
|
|
213
|
+
showLoading
|
|
214
|
+
/>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Control Components
|
|
218
|
+
|
|
219
|
+
**`<SignedIn>`** - Only renders when user is authenticated
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
<SignedIn>
|
|
223
|
+
<Dashboard />
|
|
224
|
+
</SignedIn>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**`<SignedOut>`** - Only renders when user is NOT authenticated
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
<SignedOut>
|
|
231
|
+
<SignIn />
|
|
232
|
+
</SignedOut>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**`<Protect>`** - Protects content and redirects if needed
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
<Protect fallback={<SignIn />}>
|
|
239
|
+
<ProtectedContent />
|
|
240
|
+
</Protect>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**`<RedirectToSignIn />`** - Immediately redirects to sign in
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
<RedirectToSignIn />
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Hooks
|
|
250
|
+
|
|
251
|
+
### `useAuth()` (Alias for `useHyperyAuth()`)
|
|
252
|
+
|
|
253
|
+
Full auth context with methods. Matches Clerk's API pattern.
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
const {
|
|
257
|
+
user, // Current user
|
|
258
|
+
isAuthenticated, // Auth status
|
|
259
|
+
isLoading, // Loading state
|
|
260
|
+
error, // Error message
|
|
261
|
+
login, // Initiate login
|
|
262
|
+
logout, // Sign out
|
|
263
|
+
refreshAuth, // Manually refresh
|
|
264
|
+
getAccessToken, // Get valid access token
|
|
265
|
+
} = useAuth();
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### `useUser()`
|
|
269
|
+
|
|
270
|
+
Access current user data only.
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
const { user, isLoading } = useUser();
|
|
274
|
+
|
|
275
|
+
// user object contains:
|
|
276
|
+
// - id: string
|
|
277
|
+
// - name: string
|
|
278
|
+
// - email: string
|
|
279
|
+
// - image?: string
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### `useHyperyAuth()`
|
|
283
|
+
|
|
284
|
+
Same as `useAuth()`, but with explicit naming.
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
const auth = useHyperyAuth();
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Advanced Usage
|
|
291
|
+
|
|
292
|
+
### Custom storage
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
<HyperyProvider
|
|
296
|
+
config={{
|
|
297
|
+
// ...other config
|
|
298
|
+
storage: 'sessionStorage', // or 'memory' for SSR
|
|
299
|
+
}}
|
|
300
|
+
>
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Manual token management
|
|
304
|
+
|
|
305
|
+
```tsx
|
|
306
|
+
import { useHyperyAuth } from '@hyperyai/sdk';
|
|
307
|
+
|
|
308
|
+
function MyComponent() {
|
|
309
|
+
const { getAccessToken } = useHyperyAuth();
|
|
310
|
+
|
|
311
|
+
const makeAuthenticatedRequest = async () => {
|
|
312
|
+
const token = await getAccessToken();
|
|
313
|
+
|
|
314
|
+
const response = await fetch('/api/data', {
|
|
315
|
+
headers: {
|
|
316
|
+
Authorization: `Bearer ${token}`,
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
return response.json();
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Custom UI
|
|
326
|
+
|
|
327
|
+
Build your own UI using the hooks:
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
import { useHyperyAuth } from '@hyperyai/sdk';
|
|
331
|
+
|
|
332
|
+
function CustomLoginButton() {
|
|
333
|
+
const { login, isLoading } = useHyperyAuth();
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
<button
|
|
337
|
+
onClick={login}
|
|
338
|
+
disabled={isLoading}
|
|
339
|
+
className="my-custom-button"
|
|
340
|
+
>
|
|
341
|
+
{isLoading ? 'Loading...' : 'Sign in'}
|
|
342
|
+
</button>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Environment Variables
|
|
348
|
+
|
|
349
|
+
Create a `.env.local` file:
|
|
350
|
+
|
|
351
|
+
```env
|
|
352
|
+
NEXT_PUBLIC_OAUTH_CLIENT_ID=your_client_id
|
|
353
|
+
NEXT_PUBLIC_REDIRECT_URI=http://localhost:3000/callback
|
|
354
|
+
NEXT_PUBLIC_AUTH_URL=https://api.hypery.ai
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Error handling: payment & auth modals
|
|
358
|
+
|
|
359
|
+
When a gateway request is blocked mid-flow, the SDK lets you pop a **payment**
|
|
360
|
+
modal (out of credits / spending limit / card issue) or trigger **re-auth**
|
|
361
|
+
(expired/invalid token). This is **opt-in** — the SDK does not install a global
|
|
362
|
+
fetch interceptor, so you wire it into your own API calls.
|
|
363
|
+
|
|
364
|
+
Errors arrive as a JSON envelope. Classify on `error.code` (status is a
|
|
365
|
+
fallback only — spending-limit is `429`, insufficient-credits/payment is `402`,
|
|
366
|
+
auth is `401`):
|
|
367
|
+
|
|
368
|
+
```jsonc
|
|
369
|
+
{ "error": { "code": "INSUFFICIENT_CREDITS", "type": "insufficient_credits_error" } }
|
|
370
|
+
{ "error": { "code": "SPENDING_LIMIT_EXCEEDED", "type": "spending_limit_error" } }
|
|
371
|
+
{ "error": { "code": "PAYMENT_METHOD_REQUIRED", "type": "payment_method_required_error" } }
|
|
372
|
+
{ "error": { "code": "PAYMENT_DECLINED", "type": "payment_declined_error" } }
|
|
373
|
+
{ "error": { "code": "UNAUTHENTICATED", "type": "authentication_error" } }
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
`useError().setError(body)` accepts the raw `{ error: { code } }` envelope **or**
|
|
377
|
+
an already-unwrapped object, and falls back to the HTTP status (`402`/`401`) when
|
|
378
|
+
the body has no recognized `code`. It exposes `isBillingRestriction` (any of the
|
|
379
|
+
payment/credit/limit cases) and `isAuth` (a `401`) so you can drive
|
|
380
|
+
`RestrictionModal` and `AuthModal` respectively. The guards
|
|
381
|
+
`isBillingRestriction(body)` and `isAuthError(body)` are also exported for use
|
|
382
|
+
outside the hook.
|
|
383
|
+
|
|
384
|
+
## Examples
|
|
385
|
+
|
|
386
|
+
See the `/apps/chat` and `/apps/imagine` directories for complete working examples.
|
|
387
|
+
|
|
388
|
+
## License
|
|
389
|
+
|
|
390
|
+
MIT
|
|
391
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthButton Component
|
|
3
|
+
* Simple button that triggers authentication modal
|
|
4
|
+
* Drop this anywhere in your app for instant auth
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
export interface AuthButtonProps {
|
|
8
|
+
/** Button text */
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
/** Button variant */
|
|
11
|
+
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
|
|
12
|
+
/** Button size */
|
|
13
|
+
size?: 'sm' | 'md' | 'lg';
|
|
14
|
+
/** Custom className */
|
|
15
|
+
className?: string;
|
|
16
|
+
/** Initial auth mode */
|
|
17
|
+
mode?: 'signin' | 'signup';
|
|
18
|
+
/** Callback after successful auth */
|
|
19
|
+
onSuccess?: () => void;
|
|
20
|
+
/** Show social login options */
|
|
21
|
+
showSocial?: boolean;
|
|
22
|
+
/** Show email/password form */
|
|
23
|
+
showEmailPassword?: boolean;
|
|
24
|
+
/** Custom branding for modal */
|
|
25
|
+
branding?: {
|
|
26
|
+
logo?: string;
|
|
27
|
+
appName?: string;
|
|
28
|
+
primaryColor?: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Button that opens authentication modal
|
|
33
|
+
* Perfect for navbar, landing pages, or anywhere you need auth
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* // Simple usage
|
|
38
|
+
* <AuthButton>Sign In</AuthButton>
|
|
39
|
+
*
|
|
40
|
+
* // With customization
|
|
41
|
+
* <AuthButton
|
|
42
|
+
* variant="primary"
|
|
43
|
+
* mode="signup"
|
|
44
|
+
* onSuccess={() => router.push('/dashboard')}
|
|
45
|
+
* branding={{ appName: 'My App', primaryColor: '#8b5cf6' }}
|
|
46
|
+
* >
|
|
47
|
+
* Get Started
|
|
48
|
+
* </AuthButton>
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function AuthButton({ children, variant, size, className, mode, onSuccess, showSocial, showEmailPassword, branding, }: AuthButtonProps): React.JSX.Element;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthButton Component
|
|
3
|
+
* Simple button that triggers authentication modal
|
|
4
|
+
* Drop this anywhere in your app for instant auth
|
|
5
|
+
*/
|
|
6
|
+
'use client';
|
|
7
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { useState } from 'react';
|
|
9
|
+
import { AuthModal } from './AuthModal';
|
|
10
|
+
/**
|
|
11
|
+
* Button that opens authentication modal
|
|
12
|
+
* Perfect for navbar, landing pages, or anywhere you need auth
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* // Simple usage
|
|
17
|
+
* <AuthButton>Sign In</AuthButton>
|
|
18
|
+
*
|
|
19
|
+
* // With customization
|
|
20
|
+
* <AuthButton
|
|
21
|
+
* variant="primary"
|
|
22
|
+
* mode="signup"
|
|
23
|
+
* onSuccess={() => router.push('/dashboard')}
|
|
24
|
+
* branding={{ appName: 'My App', primaryColor: '#8b5cf6' }}
|
|
25
|
+
* >
|
|
26
|
+
* Get Started
|
|
27
|
+
* </AuthButton>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function AuthButton({ children = 'Sign In', variant = 'primary', size = 'md', className = '', mode = 'signin', onSuccess, showSocial = true, showEmailPassword = true, branding, }) {
|
|
31
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
32
|
+
const sizeClasses = {
|
|
33
|
+
sm: 'px-4 py-2 text-sm',
|
|
34
|
+
md: 'px-6 py-2.5 text-base',
|
|
35
|
+
lg: 'px-8 py-3 text-lg',
|
|
36
|
+
};
|
|
37
|
+
const variantClasses = {
|
|
38
|
+
primary: 'bg-purple-600 hover:bg-purple-700 text-white shadow-md hover:shadow-lg',
|
|
39
|
+
secondary: 'bg-gray-600 hover:bg-gray-700 text-white shadow-md hover:shadow-lg',
|
|
40
|
+
outline: 'border-2 border-purple-600 text-purple-600 hover:bg-purple-50 dark:hover:bg-purple-950/30',
|
|
41
|
+
ghost: 'hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300',
|
|
42
|
+
};
|
|
43
|
+
const handleClick = () => {
|
|
44
|
+
console.log('AuthButton clicked! Opening modal...', { isOpen, mode });
|
|
45
|
+
setIsOpen(true);
|
|
46
|
+
};
|
|
47
|
+
console.log('AuthButton render:', { isOpen, mode, variant, size });
|
|
48
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { onClick: handleClick, type: "button", className: `
|
|
49
|
+
${sizeClasses[size]}
|
|
50
|
+
${variantClasses[variant]}
|
|
51
|
+
rounded-lg font-medium transition-all
|
|
52
|
+
${className}
|
|
53
|
+
`, children: children }), _jsx(AuthModal, { isOpen: isOpen, onClose: () => {
|
|
54
|
+
console.log('AuthModal closing...');
|
|
55
|
+
setIsOpen(false);
|
|
56
|
+
}, initialMode: mode, onSuccess: () => {
|
|
57
|
+
setIsOpen(false);
|
|
58
|
+
onSuccess?.();
|
|
59
|
+
}, showSocial: showSocial, showEmailPassword: showEmailPassword, branding: branding })] }));
|
|
60
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthModal Component
|
|
3
|
+
* Modal dialog for authentication using shadcn Dialog
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
export interface AuthModalProps {
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
initialMode?: 'signin' | 'signup';
|
|
10
|
+
onSuccess?: () => void;
|
|
11
|
+
onError?: (error: string) => void;
|
|
12
|
+
showSocial?: boolean;
|
|
13
|
+
showEmailPassword?: boolean;
|
|
14
|
+
branding?: {
|
|
15
|
+
logo?: string;
|
|
16
|
+
appName?: string;
|
|
17
|
+
primaryColor?: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export declare function AuthModal({ isOpen, onClose, initialMode, onSuccess, onError, showSocial, showEmailPassword, branding, }: AuthModalProps): React.JSX.Element;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthModal Component
|
|
3
|
+
* Modal dialog for authentication using shadcn Dialog
|
|
4
|
+
*/
|
|
5
|
+
'use client';
|
|
6
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { useState } from 'react';
|
|
8
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './ui/dialog';
|
|
9
|
+
import { useHyperyAuth } from '../lib/context';
|
|
10
|
+
export function AuthModal({ isOpen, onClose, initialMode = 'signin', onSuccess, onError, showSocial = true, showEmailPassword = false, branding, }) {
|
|
11
|
+
const { login } = useHyperyAuth();
|
|
12
|
+
const [mode, setMode] = useState(initialMode);
|
|
13
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
14
|
+
const [loadingProvider, setLoadingProvider] = useState(null);
|
|
15
|
+
const [email, setEmail] = useState('');
|
|
16
|
+
const [password, setPassword] = useState('');
|
|
17
|
+
const [name, setName] = useState('');
|
|
18
|
+
const [error, setError] = useState('');
|
|
19
|
+
const handleSocialSignIn = async (provider) => {
|
|
20
|
+
console.log('[AuthModal] Social button clicked:', provider);
|
|
21
|
+
setLoadingProvider(provider);
|
|
22
|
+
setError('');
|
|
23
|
+
try {
|
|
24
|
+
console.log('[AuthModal] Calling login()...');
|
|
25
|
+
await login();
|
|
26
|
+
console.log('[AuthModal] Login completed');
|
|
27
|
+
if (onSuccess) {
|
|
28
|
+
onSuccess();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
console.error('[AuthModal] Login failed:', err);
|
|
33
|
+
const errorMessage = err instanceof Error ? err.message : `${provider} sign in failed`;
|
|
34
|
+
setError(errorMessage);
|
|
35
|
+
if (onError) {
|
|
36
|
+
onError(errorMessage);
|
|
37
|
+
}
|
|
38
|
+
setLoadingProvider(null);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const handleEmailSubmit = (e) => {
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
setError('');
|
|
44
|
+
setIsLoading(true);
|
|
45
|
+
if (!email || !password) {
|
|
46
|
+
setError('Please fill in all fields');
|
|
47
|
+
setIsLoading(false);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (mode === 'signup' && !name) {
|
|
51
|
+
setError('Please enter your name');
|
|
52
|
+
setIsLoading(false);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
setError('Direct email authentication coming soon. Please use social sign-in.');
|
|
56
|
+
setIsLoading(false);
|
|
57
|
+
};
|
|
58
|
+
const primaryColor = branding?.primaryColor || '#8b5cf6';
|
|
59
|
+
return (_jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && onClose(), children: _jsxs(DialogContent, { className: "sm:max-w-md bg-white dark:bg-gray-900 text-gray-900 dark:text-white", children: [_jsxs(DialogHeader, { children: [branding?.logo && (_jsx("img", { src: branding.logo, alt: "Logo", className: "h-12 mx-auto mb-4" })), _jsx(DialogTitle, { className: "text-center text-2xl text-gray-900 dark:text-white", children: mode === 'signin' ? 'Welcome back' : 'Create your account' }), _jsx(DialogDescription, { className: "text-center text-sm text-gray-600 dark:text-gray-400", children: mode === 'signin'
|
|
60
|
+
? `Sign in to ${branding?.appName || 'continue'}`
|
|
61
|
+
: `Get started with ${branding?.appName || 'your account'}` })] }), _jsxs("div", { className: "space-y-6 py-4", children: [error && (_jsx("div", { className: "p-3 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", children: error })), showSocial && (_jsxs("div", { className: "space-y-2", 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-2 px-3 rounded flex items-center justify-center gap-2 text-sm font-medium transition-all disabled:opacity-50", children: [loadingProvider === 'google' ? (_jsx("div", { className: "w-4 h-4 border-2 border-gray-300 border-t-transparent rounded-full animate-spin" })) : (_jsxs("svg", { className: "w-4 h-4", 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-2 px-3 rounded flex items-center justify-center gap-2 text-sm font-medium transition-all disabled:opacity-50", children: [loadingProvider === 'github' ? (_jsx("div", { className: "w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" })) : (_jsx("svg", { className: "w-4 h-4", 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", 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-xs uppercase", children: _jsx("span", { className: "bg-white dark:bg-gray-900 px-2 text-gray-500 dark:text-gray-400", children: "Or continue with email" }) })] })), showEmailPassword && (_jsxs("form", { onSubmit: handleEmailSubmit, className: "space-y-4", children: [mode === 'signup' && (_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: "Full name" }), _jsx("input", { type: "text", value: name, onChange: (e) => setName(e.target.value), className: "w-full px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-white", placeholder: "John Doe", disabled: isLoading })] })), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: "Email" }), _jsx("input", { type: "email", value: email, onChange: (e) => setEmail(e.target.value), className: "w-full px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-white", placeholder: "you@example.com", disabled: isLoading })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: "Password" }), _jsx("input", { type: "password", value: password, onChange: (e) => setPassword(e.target.value), className: "w-full px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-white", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", disabled: isLoading })] }), _jsx("button", { type: "submit", disabled: isLoading, className: "w-full py-2 px-4 rounded-md font-medium text-white transition-all", style: { backgroundColor: primaryColor }, children: isLoading ? 'Processing...' : mode === 'signin' ? 'Sign in' : 'Create account' })] })), _jsxs("div", { className: "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-medium hover:underline", style: { color: primaryColor }, children: mode === 'signin' ? 'Sign up' : 'Sign in' })] })] })] }) }));
|
|
62
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
import type React from "react";
|
|
11
|
+
import { type BuySuccess } from "../hooks/useMarketplace";
|
|
12
|
+
import type { BrandingConfig, ParsedError } from "../types";
|
|
13
|
+
export interface BuyButtonProps {
|
|
14
|
+
/** Seller app the purchase is credited to. */
|
|
15
|
+
appId: string;
|
|
16
|
+
/** Amount to charge the buyer, in cents. */
|
|
17
|
+
amountCents: number;
|
|
18
|
+
/** Optional human-readable description of the purchase. */
|
|
19
|
+
description?: string;
|
|
20
|
+
/** Button label. Defaults to a price-formatted "Buy · $X.XX". */
|
|
21
|
+
label?: React.ReactNode;
|
|
22
|
+
/**
|
|
23
|
+
* Require an explicit per-transaction confirmation before charging (the card
|
|
24
|
+
* is charged off-session, so this gives the buyer a clear "yes"). Defaults to
|
|
25
|
+
* true: the first click arms the button ("Confirm · $X.XX" + Cancel), the
|
|
26
|
+
* second click charges. Set false to charge on a single click.
|
|
27
|
+
*/
|
|
28
|
+
requireConfirmation?: boolean;
|
|
29
|
+
/** Called with the successful checkout result. */
|
|
30
|
+
onSuccess?: (result: BuySuccess) => void;
|
|
31
|
+
/** Called with a classified error for failures other than needs-a-card. */
|
|
32
|
+
onError?: (error: ParsedError) => void;
|
|
33
|
+
/** Custom className appended to the button. */
|
|
34
|
+
className?: string;
|
|
35
|
+
/** Optional branding (primaryColor is used as the button color). */
|
|
36
|
+
branding?: BrandingConfig;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Drop-in checkout button for the Hypery marketplace.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```tsx
|
|
43
|
+
* <BuyButton appId="app_123" amountCents={499} description="Pro upgrade"
|
|
44
|
+
* onSuccess={(r) => console.log('paid', r.paymentIntentId)} />
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function BuyButton({ appId, amountCents, description, label, onSuccess, onError, className, branding, requireConfirmation, }: BuyButtonProps): React.JSX.Element;
|