azirid-react 0.6.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/README.md +1311 -0
- package/dist/index.cjs +3333 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1570 -0
- package/dist/index.d.ts +1570 -0
- package/dist/index.js +3275 -0
- package/dist/index.js.map +1 -0
- package/dist/next.cjs +189 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +180 -0
- package/dist/next.d.ts +180 -0
- package/dist/next.js +175 -0
- package/dist/next.js.map +1 -0
- package/dist/server.cjs +58 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +102 -0
- package/dist/server.d.ts +102 -0
- package/dist/server.js +55 -0
- package/dist/server.js.map +1 -0
- package/dist/styles.css +2 -0
- package/package.json +114 -0
package/README.md
ADDED
|
@@ -0,0 +1,1311 @@
|
|
|
1
|
+
# azirid-react
|
|
2
|
+
|
|
3
|
+
Authentication components and hooks for React and Next.js — powered by [TanStack Query](https://tanstack.com/query) and [Zod](https://zod.dev).
|
|
4
|
+
|
|
5
|
+
Drop-in `<LoginForm>`, `<SignupForm>` and more, **or** use the headless hooks to build fully custom UIs.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install azirid-react
|
|
11
|
+
# or
|
|
12
|
+
pnpm add azirid-react
|
|
13
|
+
# or
|
|
14
|
+
yarn add azirid-react
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Peer dependencies
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install react react-dom @tanstack/react-query
|
|
21
|
+
# Tailwind CSS is optional – only needed if you use the built-in components
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Architecture: Proxy vs Direct Mode
|
|
27
|
+
|
|
28
|
+
`azirid-react` supports two connection modes to the Azirid API. Choose the one that fits your stack:
|
|
29
|
+
|
|
30
|
+
| | Proxy mode | Direct mode |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| **Best for** | Next.js (App Router) | React SPA (Vite, CRA, Remix) |
|
|
33
|
+
| **Security** | First-party cookies, no CORS | Requires CORS on API |
|
|
34
|
+
| **Setup** | Route handler + Provider | Provider only |
|
|
35
|
+
| **How it works** | Browser → your app `/api/auth/*` → Azirid API | Browser → Azirid API directly |
|
|
36
|
+
|
|
37
|
+
### Proxy mode (recommended for Next.js)
|
|
38
|
+
|
|
39
|
+
Requests go to your Next.js app's `/api/auth/*` route handler, which securely proxies them to the Azirid API. Cookies are first-party (same domain), so no CORS configuration is needed.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
// No apiUrl prop → proxy mode is activated automatically
|
|
43
|
+
<AziridProvider publishableKey="pk_live_...">
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Direct mode (for React SPA / Vite)
|
|
47
|
+
|
|
48
|
+
Requests go directly to the Azirid API. Requires CORS to be configured on the API server.
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
// apiUrl prop → direct mode
|
|
52
|
+
<AziridProvider apiUrl="https://api.azirid.com" publishableKey="pk_live_...">
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Quick Start — Next.js (Proxy Mode)
|
|
58
|
+
|
|
59
|
+
### 1. Create the route handler
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
// app/api/auth/[...path]/route.ts
|
|
63
|
+
export { GET, POST, PUT, PATCH, DELETE } from 'azirid-react/next'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
That's it. The SDK handles proxy logic, cookie fixing, and header forwarding internally. Works with Next.js 14, 15, and 16+ automatically.
|
|
67
|
+
|
|
68
|
+
### 2. Set the API URL (optional)
|
|
69
|
+
|
|
70
|
+
```env
|
|
71
|
+
# .env (server-side only — never exposed to the browser)
|
|
72
|
+
# Default: https://api.azirid.com
|
|
73
|
+
# For local development with the API running locally:
|
|
74
|
+
AZIRID_API_URL=http://localhost:3000
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. Wrap your app with `<AziridProvider>`
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// app/layout.tsx
|
|
81
|
+
import { AziridProvider } from 'azirid-react'
|
|
82
|
+
|
|
83
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
84
|
+
return (
|
|
85
|
+
<AziridProvider
|
|
86
|
+
publishableKey={process.env.NEXT_PUBLIC_AZIRID_PK!}
|
|
87
|
+
onLoginSuccess={(data) => console.log('Logged in:', data.user)}
|
|
88
|
+
onLogoutSuccess={() => console.log('Logged out')}
|
|
89
|
+
onSessionExpired={() => (window.location.href = '/login')}
|
|
90
|
+
>
|
|
91
|
+
{children}
|
|
92
|
+
</AziridProvider>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 4. Add the login page
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
// app/login/page.tsx
|
|
101
|
+
import { LoginForm } from 'azirid-react'
|
|
102
|
+
|
|
103
|
+
export default function LoginPage() {
|
|
104
|
+
return <LoginForm />
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Quick Start — React SPA (Direct Mode)
|
|
111
|
+
|
|
112
|
+
### 1. Wrap your app with `<AziridProvider>`
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
// main.tsx (Vite / CRA)
|
|
116
|
+
import { AziridProvider } from 'azirid-react'
|
|
117
|
+
|
|
118
|
+
createRoot(document.getElementById('root')!).render(
|
|
119
|
+
<AziridProvider
|
|
120
|
+
apiUrl={import.meta.env.VITE_AZIRID_API_URL || 'https://api.azirid.com'}
|
|
121
|
+
publishableKey={import.meta.env.VITE_AZIRID_PK}
|
|
122
|
+
onLoginSuccess={(data) => console.log('Logged in:', data.user)}
|
|
123
|
+
>
|
|
124
|
+
<App />
|
|
125
|
+
</AziridProvider>,
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 2. Configure your environment
|
|
130
|
+
|
|
131
|
+
```env
|
|
132
|
+
# .env
|
|
133
|
+
VITE_AZIRID_API_URL=https://api.azirid.com
|
|
134
|
+
VITE_AZIRID_PK=pk_live_...
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 3. Use the forms or hooks
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
import { LoginForm } from 'azirid-react'
|
|
141
|
+
|
|
142
|
+
export default function LoginPage() {
|
|
143
|
+
return <LoginForm />
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
No route handler or proxy needed — requests go directly to the API.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Headless hooks
|
|
152
|
+
|
|
153
|
+
All hooks require `<AziridProvider>` in the tree.
|
|
154
|
+
|
|
155
|
+
### `useAzirid` — session state
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
import { useAzirid } from 'azirid-react'
|
|
159
|
+
|
|
160
|
+
function Navbar() {
|
|
161
|
+
const { user, isAuthenticated, isLoading, login, logout } = useAzirid()
|
|
162
|
+
|
|
163
|
+
if (isLoading) return <Spinner />
|
|
164
|
+
|
|
165
|
+
return isAuthenticated ? (
|
|
166
|
+
<div>
|
|
167
|
+
<span>Hello, {user!.email}</span>
|
|
168
|
+
<button onClick={logout}>Sign out</button>
|
|
169
|
+
</div>
|
|
170
|
+
) : (
|
|
171
|
+
<button onClick={() => login({ email: '...', password: '...' })}>Sign in</button>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### `useLogin`
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import { useLogin } from 'azirid-react'
|
|
180
|
+
|
|
181
|
+
function CustomLoginForm() {
|
|
182
|
+
const { login, isLoading, error } = useLogin({
|
|
183
|
+
onSuccess: (data) => console.log(data.user),
|
|
184
|
+
onError: (msg) => console.error(msg),
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<form
|
|
189
|
+
onSubmit={(e) => {
|
|
190
|
+
e.preventDefault()
|
|
191
|
+
const fd = new FormData(e.currentTarget)
|
|
192
|
+
login({
|
|
193
|
+
email: fd.get('email') as string,
|
|
194
|
+
password: fd.get('password') as string,
|
|
195
|
+
})
|
|
196
|
+
}}
|
|
197
|
+
>
|
|
198
|
+
<input name="email" type="email" />
|
|
199
|
+
<input name="password" type="password" />
|
|
200
|
+
{error && <p>{error}</p>}
|
|
201
|
+
<button disabled={isLoading}>Sign in</button>
|
|
202
|
+
</form>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### `useSignup`
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
import { useSignup } from 'azirid-react'
|
|
211
|
+
|
|
212
|
+
const { signup, isLoading, error } = useSignup({
|
|
213
|
+
onSuccess: (data) => console.log('Registered:', data.user),
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
signup({ email: 'user@example.com', password: 'secret' })
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### `useLogout`
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
import { useLogout } from 'azirid-react'
|
|
223
|
+
|
|
224
|
+
const { logout, isLoading } = useLogout({
|
|
225
|
+
onSuccess: () => router.push('/login'),
|
|
226
|
+
})
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### `useSession`
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
import { useSession } from 'azirid-react'
|
|
233
|
+
|
|
234
|
+
const { user, accessToken, isAuthenticated } = useSession()
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### `useMagicLink`
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
import { useMagicLink } from 'azirid-react'
|
|
241
|
+
|
|
242
|
+
const { requestMagicLink, verifyMagicLink, isLoading } = useMagicLink()
|
|
243
|
+
|
|
244
|
+
requestMagicLink({ email: 'user@example.com' })
|
|
245
|
+
verifyMagicLink({ token: '...' })
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### `useSocialLogin`
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
import { useSocialLogin } from 'azirid-react'
|
|
252
|
+
|
|
253
|
+
const { loginWithProvider, isLoading } = useSocialLogin()
|
|
254
|
+
|
|
255
|
+
loginWithProvider({ provider: 'google' }) // "google" | "github"
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### `usePasskeys`
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
import { usePasskeys } from 'azirid-react'
|
|
262
|
+
|
|
263
|
+
const { passkeys, registerPasskey, removePasskey, isLoading } = usePasskeys()
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### `useChangePassword`
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
import { useChangePassword } from 'azirid-react'
|
|
270
|
+
|
|
271
|
+
const { changePassword, isLoading, error } = useChangePassword()
|
|
272
|
+
|
|
273
|
+
changePassword({ currentPassword: 'old', newPassword: 'new' })
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### `useBootstrap`
|
|
277
|
+
|
|
278
|
+
Manually re-run the session bootstrap (useful after SSO redirects).
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
import { useBootstrap } from 'azirid-react'
|
|
282
|
+
|
|
283
|
+
const { bootstrap, isBootstrapping } = useBootstrap()
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### `useRefresh`
|
|
287
|
+
|
|
288
|
+
Manually refresh the access token.
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
import { useRefresh } from 'azirid-react'
|
|
292
|
+
|
|
293
|
+
const { refresh } = useRefresh()
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### `useAziridClient`
|
|
297
|
+
|
|
298
|
+
Access the raw `AccessClient` instance for custom API calls.
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
import { useAziridClient } from 'azirid-react'
|
|
302
|
+
|
|
303
|
+
function CustomAction() {
|
|
304
|
+
const client = useAziridClient()
|
|
305
|
+
|
|
306
|
+
async function fetchCustomData() {
|
|
307
|
+
const data = await client.get('/v1/custom-endpoint')
|
|
308
|
+
console.log(data)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return <button onClick={fetchCustomData}>Fetch</button>
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### `useFormState`
|
|
316
|
+
|
|
317
|
+
Headless form hook with Zod validation. Powers the built-in form components — use it to build fully custom forms.
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
import { useFormState, loginSchema } from 'azirid-react'
|
|
321
|
+
|
|
322
|
+
function CustomForm() {
|
|
323
|
+
const { values, errors, isSubmitting, handleChange, handleSubmit, reset } = useFormState(
|
|
324
|
+
{ email: '', password: '' },
|
|
325
|
+
loginSchema,
|
|
326
|
+
async (values) => {
|
|
327
|
+
// Submit logic
|
|
328
|
+
},
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
return (
|
|
332
|
+
<form onSubmit={handleSubmit}>
|
|
333
|
+
<input value={values.email} onChange={handleChange('email')} />
|
|
334
|
+
{errors.find((e) => e.field === 'email')?.message}
|
|
335
|
+
<button disabled={isSubmitting}>Submit</button>
|
|
336
|
+
</form>
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### `usePasswordToggle`
|
|
342
|
+
|
|
343
|
+
Simple toggle between `"password"` and `"text"` input types.
|
|
344
|
+
|
|
345
|
+
```tsx
|
|
346
|
+
import { usePasswordToggle } from 'azirid-react'
|
|
347
|
+
|
|
348
|
+
function PasswordInput() {
|
|
349
|
+
const { visible, toggle, type } = usePasswordToggle()
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<div>
|
|
353
|
+
<input type={type} name="password" />
|
|
354
|
+
<button type="button" onClick={toggle}>
|
|
355
|
+
{visible ? 'Hide' : 'Show'}
|
|
356
|
+
</button>
|
|
357
|
+
</div>
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Zod Schemas
|
|
363
|
+
|
|
364
|
+
The SDK exports pre-built Zod schemas with Spanish validation messages, plus factory functions for custom messages.
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
import {
|
|
368
|
+
loginSchema,
|
|
369
|
+
signupSchema,
|
|
370
|
+
changePasswordSchema,
|
|
371
|
+
magicLinkRequestSchema,
|
|
372
|
+
magicLinkVerifySchema,
|
|
373
|
+
socialLoginSchema,
|
|
374
|
+
passkeyRegisterStartSchema,
|
|
375
|
+
} from 'azirid-react'
|
|
376
|
+
|
|
377
|
+
// Default schemas (Spanish messages)
|
|
378
|
+
loginSchema.parse({ email: 'user@example.com', password: 'secret' })
|
|
379
|
+
|
|
380
|
+
// Factory functions for custom messages
|
|
381
|
+
import { createLoginSchema, createSignupSchema } from 'azirid-react'
|
|
382
|
+
|
|
383
|
+
const customSchema = createLoginSchema({
|
|
384
|
+
emailRequired: 'Email is required',
|
|
385
|
+
emailInvalid: 'Must be a valid email',
|
|
386
|
+
passwordRequired: 'Password is required',
|
|
387
|
+
passwordMinLength: 'At least 10 characters',
|
|
388
|
+
})
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
| Schema | Fields |
|
|
392
|
+
| --- | --- |
|
|
393
|
+
| `loginSchema` | `email`, `password` |
|
|
394
|
+
| `signupSchema` | `email`, `password`, `acceptedTosVersion?`, `acceptedPrivacyVersion?` |
|
|
395
|
+
| `changePasswordSchema` | `currentPassword`, `newPassword` |
|
|
396
|
+
| `magicLinkRequestSchema` | `email` |
|
|
397
|
+
| `magicLinkVerifySchema` | `token` |
|
|
398
|
+
| `socialLoginSchema` | `provider`, `providerAccountId`, `email`, `emailVerified?`, ... |
|
|
399
|
+
| `passkeyRegisterStartSchema` | `deviceName?` |
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Billing & Payments
|
|
404
|
+
|
|
405
|
+
All billing hooks require `<AziridProvider>` in the tree and an authenticated user.
|
|
406
|
+
|
|
407
|
+
### `usePlans`
|
|
408
|
+
|
|
409
|
+
Fetch all available billing plans for the current app.
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
import { usePlans } from 'azirid-react'
|
|
413
|
+
|
|
414
|
+
function PricingPage() {
|
|
415
|
+
const { data: plans, isLoading } = usePlans()
|
|
416
|
+
|
|
417
|
+
if (isLoading) return <Spinner />
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
<ul>
|
|
421
|
+
{plans?.map((plan) => (
|
|
422
|
+
<li key={plan.id}>
|
|
423
|
+
{plan.name} — ${(plan.amount / 100).toFixed(2)}/{plan.interval.toLowerCase()}
|
|
424
|
+
</li>
|
|
425
|
+
))}
|
|
426
|
+
</ul>
|
|
427
|
+
)
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### `useSubscription`
|
|
432
|
+
|
|
433
|
+
Get the current user's active subscription.
|
|
434
|
+
|
|
435
|
+
```tsx
|
|
436
|
+
import { useSubscription } from 'azirid-react'
|
|
437
|
+
|
|
438
|
+
function SubscriptionStatus() {
|
|
439
|
+
const { data: sub } = useSubscription()
|
|
440
|
+
|
|
441
|
+
if (!sub) return <p>No active subscription</p>
|
|
442
|
+
|
|
443
|
+
return (
|
|
444
|
+
<p>
|
|
445
|
+
{sub.plan.name} — {sub.status}
|
|
446
|
+
{sub.cancelAtPeriodEnd && ' (cancels at period end)'}
|
|
447
|
+
</p>
|
|
448
|
+
)
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### `useCheckout`
|
|
453
|
+
|
|
454
|
+
Initiate a checkout session. Auto-redirects to the payment provider on success (unless the provider is `MANUAL_TRANSFER` or `PAYPHONE`).
|
|
455
|
+
|
|
456
|
+
```tsx
|
|
457
|
+
import { useCheckout } from 'azirid-react'
|
|
458
|
+
|
|
459
|
+
function UpgradeButton({ planId }: { planId: string }) {
|
|
460
|
+
const { checkout, isPending } = useCheckout({
|
|
461
|
+
onSuccess: (data) => console.log('Checkout created:', data),
|
|
462
|
+
onError: (err) => console.error(err),
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
return (
|
|
466
|
+
<button
|
|
467
|
+
disabled={isPending}
|
|
468
|
+
onClick={() =>
|
|
469
|
+
checkout({
|
|
470
|
+
planId,
|
|
471
|
+
successUrl: `${window.location.origin}/billing?success=true`,
|
|
472
|
+
cancelUrl: `${window.location.origin}/billing`,
|
|
473
|
+
})
|
|
474
|
+
}
|
|
475
|
+
>
|
|
476
|
+
Upgrade
|
|
477
|
+
</button>
|
|
478
|
+
)
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### `useInvoices`
|
|
483
|
+
|
|
484
|
+
List all invoices for the authenticated user.
|
|
485
|
+
|
|
486
|
+
```tsx
|
|
487
|
+
import { useInvoices } from 'azirid-react'
|
|
488
|
+
|
|
489
|
+
function InvoiceHistory() {
|
|
490
|
+
const { data: invoices } = useInvoices()
|
|
491
|
+
|
|
492
|
+
return (
|
|
493
|
+
<ul>
|
|
494
|
+
{invoices?.map((inv) => (
|
|
495
|
+
<li key={inv.id}>
|
|
496
|
+
${(inv.amount / 100).toFixed(2)} — {inv.status}
|
|
497
|
+
{inv.invoiceUrl && <a href={inv.invoiceUrl}>View</a>}
|
|
498
|
+
</li>
|
|
499
|
+
))}
|
|
500
|
+
</ul>
|
|
501
|
+
)
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### `usePaymentProviders`
|
|
506
|
+
|
|
507
|
+
Fetch available payment providers for the app (e.g., Stripe, PayPal, manual transfer).
|
|
508
|
+
|
|
509
|
+
```tsx
|
|
510
|
+
import { usePaymentProviders } from 'azirid-react'
|
|
511
|
+
|
|
512
|
+
const { data: providers } = usePaymentProviders()
|
|
513
|
+
// [{ provider: 'STRIPE', checkout: true, subscriptions: true }, ...]
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### `useSubmitTransferProof`
|
|
517
|
+
|
|
518
|
+
Submit proof of a manual bank transfer payment.
|
|
519
|
+
|
|
520
|
+
```tsx
|
|
521
|
+
import { useSubmitTransferProof } from 'azirid-react'
|
|
522
|
+
|
|
523
|
+
const { submit, isPending } = useSubmitTransferProof({
|
|
524
|
+
onSuccess: (proof) => console.log('Proof submitted:', proof.id),
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
submit({
|
|
528
|
+
planId: 'plan_123',
|
|
529
|
+
fileUrl: 'https://storage.example.com/receipt.pdf',
|
|
530
|
+
amount: 9999,
|
|
531
|
+
currency: 'USD',
|
|
532
|
+
notes: 'Bank transfer from Account #1234',
|
|
533
|
+
})
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### `useTransferProofs`
|
|
537
|
+
|
|
538
|
+
List submitted transfer proofs and their review status.
|
|
539
|
+
|
|
540
|
+
```tsx
|
|
541
|
+
import { useTransferProofs } from 'azirid-react'
|
|
542
|
+
|
|
543
|
+
const { data: proofs } = useTransferProofs()
|
|
544
|
+
// [{ id, status: 'PENDING_REVIEW' | 'APPROVED' | 'REJECTED', ... }]
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### `usePayphoneConfirm`
|
|
548
|
+
|
|
549
|
+
Confirm a Payphone payment callback. Used on the Payphone return URL page.
|
|
550
|
+
|
|
551
|
+
```tsx
|
|
552
|
+
import { usePayphoneConfirm } from 'azirid-react'
|
|
553
|
+
|
|
554
|
+
const confirm = usePayphoneConfirm({
|
|
555
|
+
onSuccess: (data) => console.log('Payment confirmed:', data.status),
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
confirm.mutate({ id: 12345, clientTransactionId: 'txn_abc' })
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Billing Components
|
|
562
|
+
|
|
563
|
+
#### `PricingTable`
|
|
564
|
+
|
|
565
|
+
Drop-in pricing grid with checkout flow integrated.
|
|
566
|
+
|
|
567
|
+
```tsx
|
|
568
|
+
import { PricingTable } from 'azirid-react'
|
|
569
|
+
|
|
570
|
+
<PricingTable
|
|
571
|
+
successUrl={`${window.location.origin}/billing?success=true`}
|
|
572
|
+
cancelUrl={`${window.location.origin}/billing`}
|
|
573
|
+
columns={3}
|
|
574
|
+
onPlanSelect={(plan) => console.log('Selected:', plan.name)}
|
|
575
|
+
/>
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
| Prop | Type | Default | Description |
|
|
579
|
+
| --- | --- | --- | --- |
|
|
580
|
+
| `successUrl` | `string` | — | **Required.** Redirect URL after successful payment |
|
|
581
|
+
| `cancelUrl` | `string` | — | **Required.** Redirect URL on cancel |
|
|
582
|
+
| `columns` | `number` | `3` | Number of columns in the grid |
|
|
583
|
+
| `onPlanSelect` | `(plan) => void` | — | Called when a plan is selected |
|
|
584
|
+
| `className` | `string` | — | Additional CSS classes |
|
|
585
|
+
|
|
586
|
+
#### `PayButton`
|
|
587
|
+
|
|
588
|
+
Flexible payment button with provider selection modal. Supports both plans and payment intents.
|
|
589
|
+
|
|
590
|
+
```tsx
|
|
591
|
+
import { PayButton } from 'azirid-react'
|
|
592
|
+
|
|
593
|
+
<PayButton
|
|
594
|
+
planId="plan_123"
|
|
595
|
+
successUrl="/billing?success=true"
|
|
596
|
+
cancelUrl="/billing"
|
|
597
|
+
onSuccess={(data) => console.log('Payment success:', data)}
|
|
598
|
+
>
|
|
599
|
+
Subscribe Now
|
|
600
|
+
</PayButton>
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
| Prop | Type | Default | Description |
|
|
604
|
+
| --- | --- | --- | --- |
|
|
605
|
+
| `planId` | `string` | — | Plan to purchase (use `planId` or `intentId`) |
|
|
606
|
+
| `intentId` | `string` | — | Payment intent ID (alternative to `planId`) |
|
|
607
|
+
| `successUrl` | `string` | — | **Required.** Redirect URL after success |
|
|
608
|
+
| `cancelUrl` | `string` | — | **Required.** Redirect URL on cancel |
|
|
609
|
+
| `onSuccess` | `(data) => void` | — | Called on successful checkout |
|
|
610
|
+
| `onError` | `(error) => void` | — | Called on error |
|
|
611
|
+
| `children` | `ReactNode` | — | Button label |
|
|
612
|
+
| `disabled` | `boolean` | — | Disable the button |
|
|
613
|
+
|
|
614
|
+
#### `CheckoutButton`
|
|
615
|
+
|
|
616
|
+
Simple checkout button for a specific plan.
|
|
617
|
+
|
|
618
|
+
```tsx
|
|
619
|
+
import { CheckoutButton } from 'azirid-react'
|
|
620
|
+
|
|
621
|
+
<CheckoutButton
|
|
622
|
+
planId="plan_123"
|
|
623
|
+
successUrl="/billing?success=true"
|
|
624
|
+
cancelUrl="/billing"
|
|
625
|
+
>
|
|
626
|
+
Subscribe
|
|
627
|
+
</CheckoutButton>
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
#### `SubscriptionBadge`
|
|
631
|
+
|
|
632
|
+
Color-coded badge showing the current subscription status.
|
|
633
|
+
|
|
634
|
+
```tsx
|
|
635
|
+
import { SubscriptionBadge } from 'azirid-react'
|
|
636
|
+
|
|
637
|
+
<SubscriptionBadge />
|
|
638
|
+
// Renders: "Pro · Active" (green), "Free · Trialing" (blue), etc.
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
Status colors: ACTIVE (green), TRIALING (blue), PAST_DUE (yellow), CANCELED/UNPAID (red), INCOMPLETE (gray).
|
|
642
|
+
|
|
643
|
+
#### `InvoiceList`
|
|
644
|
+
|
|
645
|
+
Table of invoices with status badges and download links.
|
|
646
|
+
|
|
647
|
+
```tsx
|
|
648
|
+
import { InvoiceList } from 'azirid-react'
|
|
649
|
+
|
|
650
|
+
<InvoiceList />
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
#### `PayphoneCallback`
|
|
654
|
+
|
|
655
|
+
Page component for handling Payphone payment callbacks. Reads `id` and `clientTransactionId` from URL query params automatically.
|
|
656
|
+
|
|
657
|
+
```tsx
|
|
658
|
+
// app/payphone/callback/page.tsx
|
|
659
|
+
import { PayphoneCallback } from 'azirid-react'
|
|
660
|
+
|
|
661
|
+
export default function PayphoneCallbackPage() {
|
|
662
|
+
return (
|
|
663
|
+
<PayphoneCallback
|
|
664
|
+
onSuccess={(data) => console.log('Confirmed:', data.status)}
|
|
665
|
+
onError={(err) => console.error(err)}
|
|
666
|
+
/>
|
|
667
|
+
)
|
|
668
|
+
}
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
---
|
|
672
|
+
|
|
673
|
+
## Referrals
|
|
674
|
+
|
|
675
|
+
All referral hooks require `<AziridProvider>` in the tree and an authenticated user.
|
|
676
|
+
|
|
677
|
+
### `useReferral`
|
|
678
|
+
|
|
679
|
+
Fetch the current user's referral info and copy referral link to clipboard.
|
|
680
|
+
|
|
681
|
+
```tsx
|
|
682
|
+
import { useReferral } from 'azirid-react'
|
|
683
|
+
|
|
684
|
+
function ReferralSection() {
|
|
685
|
+
const { data, copyToClipboard } = useReferral()
|
|
686
|
+
|
|
687
|
+
if (!data) return null
|
|
688
|
+
|
|
689
|
+
return (
|
|
690
|
+
<div>
|
|
691
|
+
<p>Your referral code: {data.referralCode}</p>
|
|
692
|
+
<input readOnly value={data.referralUrl} />
|
|
693
|
+
<button onClick={copyToClipboard}>Copy Link</button>
|
|
694
|
+
<p>
|
|
695
|
+
{data.completedReferrals} completed / {data.totalReferred} total
|
|
696
|
+
</p>
|
|
697
|
+
</div>
|
|
698
|
+
)
|
|
699
|
+
}
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
### `useReferralStats`
|
|
703
|
+
|
|
704
|
+
Fetch detailed referral history with rewards.
|
|
705
|
+
|
|
706
|
+
```tsx
|
|
707
|
+
import { useReferralStats } from 'azirid-react'
|
|
708
|
+
|
|
709
|
+
function ReferralHistory() {
|
|
710
|
+
const { data } = useReferralStats()
|
|
711
|
+
|
|
712
|
+
return (
|
|
713
|
+
<ul>
|
|
714
|
+
{data?.referrals.map((ref) => (
|
|
715
|
+
<li key={ref.id}>
|
|
716
|
+
{ref.referredEmail} — {ref.status}
|
|
717
|
+
{ref.rewardAmount && ` ($${ref.rewardAmount})`}
|
|
718
|
+
</li>
|
|
719
|
+
))}
|
|
720
|
+
</ul>
|
|
721
|
+
)
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
### Referral Components
|
|
726
|
+
|
|
727
|
+
#### `ReferralCard`
|
|
728
|
+
|
|
729
|
+
Card displaying the referral link with copy button and stats.
|
|
730
|
+
|
|
731
|
+
```tsx
|
|
732
|
+
import { ReferralCard } from 'azirid-react'
|
|
733
|
+
|
|
734
|
+
<ReferralCard
|
|
735
|
+
title="Refer a Friend"
|
|
736
|
+
description="Share your link and earn rewards for each signup."
|
|
737
|
+
/>
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
| Prop | Type | Default | Description |
|
|
741
|
+
| --- | --- | --- | --- |
|
|
742
|
+
| `title` | `string` | `"Refer a Friend"` | Card title |
|
|
743
|
+
| `description` | `string` | — | Card description |
|
|
744
|
+
| `className` | `string` | — | Additional CSS classes |
|
|
745
|
+
|
|
746
|
+
#### `ReferralStats`
|
|
747
|
+
|
|
748
|
+
Table showing referral history with status and reward badges.
|
|
749
|
+
|
|
750
|
+
```tsx
|
|
751
|
+
import { ReferralStats } from 'azirid-react'
|
|
752
|
+
|
|
753
|
+
<ReferralStats />
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
---
|
|
757
|
+
|
|
758
|
+
## Internationalization (i18n)
|
|
759
|
+
|
|
760
|
+
Built-in support for **English** and **Spanish**. The SDK ships two complete dictionaries; pass a `locale` prop to switch languages.
|
|
761
|
+
|
|
762
|
+
```tsx
|
|
763
|
+
import { AziridProvider } from 'azirid-react'
|
|
764
|
+
;<AziridProvider publishableKey="pk_live_..." locale="en">
|
|
765
|
+
{/* All form labels, validation messages, and UI text render in English */}
|
|
766
|
+
{children}
|
|
767
|
+
</AziridProvider>
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
### Supported locales
|
|
771
|
+
|
|
772
|
+
| Locale | Language |
|
|
773
|
+
| ------ | ----------------- |
|
|
774
|
+
| `"es"` | Spanish (default) |
|
|
775
|
+
| `"en"` | English |
|
|
776
|
+
|
|
777
|
+
### Custom messages
|
|
778
|
+
|
|
779
|
+
Override any string by passing a partial `messages` object:
|
|
780
|
+
|
|
781
|
+
```tsx
|
|
782
|
+
<AziridProvider
|
|
783
|
+
publishableKey="pk_live_..."
|
|
784
|
+
locale="en"
|
|
785
|
+
messages={{
|
|
786
|
+
login: { title: 'Welcome back!', submit: 'Sign in' },
|
|
787
|
+
validation: { emailRequired: 'Please enter your email' },
|
|
788
|
+
}}
|
|
789
|
+
>
|
|
790
|
+
{children}
|
|
791
|
+
</AziridProvider>
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
### Using i18n hooks directly
|
|
795
|
+
|
|
796
|
+
```tsx
|
|
797
|
+
import { useMessages, useBranding } from 'azirid-react'
|
|
798
|
+
|
|
799
|
+
function CustomForm() {
|
|
800
|
+
const msg = useMessages() // resolved messages for current locale
|
|
801
|
+
return <label>{msg.login.emailLabel}</label>
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
### Locale-aware Zod schemas
|
|
806
|
+
|
|
807
|
+
```tsx
|
|
808
|
+
import { createLoginSchema, createSignupSchema } from 'azirid-react'
|
|
809
|
+
|
|
810
|
+
// Pass custom validation messages
|
|
811
|
+
const schema = createLoginSchema({
|
|
812
|
+
emailRequired: 'Email is required',
|
|
813
|
+
emailInvalid: 'Must be a valid email',
|
|
814
|
+
passwordRequired: 'Password is required',
|
|
815
|
+
passwordMin: 'At least 8 characters',
|
|
816
|
+
})
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
---
|
|
820
|
+
|
|
821
|
+
## Branding
|
|
822
|
+
|
|
823
|
+
The bootstrap endpoint returns branding data configured in the Azirid dashboard (Settings > Branding). The built-in form components automatically apply branding.
|
|
824
|
+
|
|
825
|
+
### Auto-branding from bootstrap
|
|
826
|
+
|
|
827
|
+
If branding is configured for your app, the forms will automatically:
|
|
828
|
+
|
|
829
|
+
- Show your **logo** (from `branding.logoUrl`) above the form
|
|
830
|
+
- Use your **display name** as the form title
|
|
831
|
+
- Apply your **primary color** to the submit button
|
|
832
|
+
- Show/hide the **"Secured by Azirid"** badge
|
|
833
|
+
|
|
834
|
+
No extra code needed — just configure branding in the dashboard.
|
|
835
|
+
|
|
836
|
+
### Overriding branding with props
|
|
837
|
+
|
|
838
|
+
Per-component props always take priority over branding context:
|
|
839
|
+
|
|
840
|
+
```tsx
|
|
841
|
+
<LoginForm
|
|
842
|
+
logo={<MyCustomLogo />} // overrides branding.logoUrl
|
|
843
|
+
title="Sign in to Acme" // overrides branding.displayName
|
|
844
|
+
submitText="Continue"
|
|
845
|
+
/>
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
### Using branding hooks
|
|
849
|
+
|
|
850
|
+
```tsx
|
|
851
|
+
import { useBranding } from 'azirid-react'
|
|
852
|
+
|
|
853
|
+
function CustomHeader() {
|
|
854
|
+
const branding = useBranding() // AppBranding | null
|
|
855
|
+
|
|
856
|
+
return (
|
|
857
|
+
<div>
|
|
858
|
+
{branding?.logoUrl && <img src={branding.logoUrl} alt="Logo" />}
|
|
859
|
+
<h1 style={{ color: branding?.primaryColor ?? '#000' }}>
|
|
860
|
+
{branding?.displayName ?? 'My App'}
|
|
861
|
+
</h1>
|
|
862
|
+
</div>
|
|
863
|
+
)
|
|
864
|
+
}
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
### "Secured by Azirid" badge
|
|
868
|
+
|
|
869
|
+
The `<SecuredByBadge />` component renders below each form. It's hidden when `branding.removeBranding` is `true` (configurable in the dashboard).
|
|
870
|
+
|
|
871
|
+
```tsx
|
|
872
|
+
import { SecuredByBadge } from "azirid-react";
|
|
873
|
+
|
|
874
|
+
// Use in custom form layouts
|
|
875
|
+
<form>
|
|
876
|
+
{/* ... your form fields ... */}
|
|
877
|
+
</form>
|
|
878
|
+
<SecuredByBadge />
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
---
|
|
882
|
+
|
|
883
|
+
## createAccessClient
|
|
884
|
+
|
|
885
|
+
Under the hood `AziridProvider` creates an `AccessClient` via `createAccessClient`. You can also create a client directly to make raw API calls.
|
|
886
|
+
|
|
887
|
+
```ts
|
|
888
|
+
import { createAccessClient, BASE_PATHS } from 'azirid-react'
|
|
889
|
+
import type { AccessClientConfig } from 'azirid-react'
|
|
890
|
+
|
|
891
|
+
// Direct mode — point to the API
|
|
892
|
+
const client = createAccessClient(
|
|
893
|
+
{
|
|
894
|
+
baseUrl: 'https://api.azirid.com',
|
|
895
|
+
basePath: BASE_PATHS.direct, // '/v1/users/auth'
|
|
896
|
+
},
|
|
897
|
+
{ publishableKey: 'pk_live_...' },
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
// Set tokens after login
|
|
901
|
+
client.setAccessToken('eyJ...')
|
|
902
|
+
client.setRefreshToken('...')
|
|
903
|
+
|
|
904
|
+
// Make arbitrary authenticated calls
|
|
905
|
+
const data = await client.get(client.paths.me)
|
|
906
|
+
const result = await client.post('/v1/custom-endpoint', { foo: 'bar' })
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
### `createAccessClient` signature
|
|
910
|
+
|
|
911
|
+
```ts
|
|
912
|
+
function createAccessClient(
|
|
913
|
+
config: AccessClientConfig,
|
|
914
|
+
appContext?: { publishableKey: string; tenantId?: string },
|
|
915
|
+
): AccessClient
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
| Param | Type | Description |
|
|
919
|
+
| ----------- | -------------------- | ---------------------------------------------------------------- |
|
|
920
|
+
| `config` | `AccessClientConfig` | `{ baseUrl, basePath?, headers? }` |
|
|
921
|
+
| `appContext` | `object` | Optional. `publishableKey` and `tenantId` |
|
|
922
|
+
|
|
923
|
+
---
|
|
924
|
+
|
|
925
|
+
## AziridProvider props
|
|
926
|
+
|
|
927
|
+
| Prop | Type | Default | Description |
|
|
928
|
+
| ------------------ | ------------------------- | -------- | ---------------------------------------------------------------------------------------------- |
|
|
929
|
+
| `children` | `ReactNode` | — | **Required.** Your app tree |
|
|
930
|
+
| `apiUrl` | `string` | — | API URL for direct mode. Omit for proxy mode (recommended in Next.js) |
|
|
931
|
+
| `publishableKey` | `string` | — | Publishable key (e.g. `pk_live_...`) |
|
|
932
|
+
| `tenantId` | `string` | — | Tenant ID for multi-tenant apps |
|
|
933
|
+
| `fetchOptions` | `Record<string, string>` | — | Extra headers to send with every request |
|
|
934
|
+
| `autoBootstrap` | `boolean` | `true` | Auto-restore session on mount |
|
|
935
|
+
| `refreshInterval` | `number` | `50000` | Token refresh interval in ms. `0` to disable |
|
|
936
|
+
| `sessionSyncUrl` | `string \| false` | auto | URL for session cookie sync. Auto-activates in dev mode. Pass `false` to disable |
|
|
937
|
+
| `onLoginSuccess` | `(data) => void` | — | Called after successful login |
|
|
938
|
+
| `onSignupSuccess` | `(data) => void` | — | Called after successful signup |
|
|
939
|
+
| `onLogoutSuccess` | `() => void` | — | Called after logout |
|
|
940
|
+
| `onSessionExpired` | `() => void` | — | Called when refresh fails |
|
|
941
|
+
| `onError` | `(msg: string) => void` | — | Called on any auth error |
|
|
942
|
+
| `locale` | `"es" \| "en"` | `"es"` | UI language for built-in forms and validation messages |
|
|
943
|
+
| `messages` | `Partial<AccessMessages>` | — | Override any i18n string (merged on top of the locale dictionary) |
|
|
944
|
+
|
|
945
|
+
---
|
|
946
|
+
|
|
947
|
+
## Next.js Integration
|
|
948
|
+
|
|
949
|
+
`azirid-react` supports **Next.js 14, 15, and 16+** with full compatibility for each version's API conventions.
|
|
950
|
+
|
|
951
|
+
### Proxy Route Handler (all versions)
|
|
952
|
+
|
|
953
|
+
Create the file `app/api/auth/[...path]/route.ts` — one line is all you need:
|
|
954
|
+
|
|
955
|
+
```ts
|
|
956
|
+
// app/api/auth/[...path]/route.ts
|
|
957
|
+
export { GET, POST, PUT, PATCH, DELETE } from 'azirid-react/next'
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
That's it. The SDK handles all the proxy logic, cookie fixing, and header forwarding internally.
|
|
961
|
+
It works with Next.js 14, 15, and 16+ automatically.
|
|
962
|
+
|
|
963
|
+
### Custom API URL or debug logging
|
|
964
|
+
|
|
965
|
+
```ts
|
|
966
|
+
// app/api/auth/[...path]/route.ts
|
|
967
|
+
import { createAziridRouteHandlers } from 'azirid-react/next'
|
|
968
|
+
|
|
969
|
+
export const { GET, POST, PUT, PATCH, DELETE } = createAziridRouteHandlers({
|
|
970
|
+
apiUrl: 'https://my-custom-api.com',
|
|
971
|
+
debug: true, // logs proxy requests to console
|
|
972
|
+
})
|
|
973
|
+
```
|
|
974
|
+
|
|
975
|
+
### Environment variable
|
|
976
|
+
|
|
977
|
+
The proxy reads `AZIRID_API_URL` (server-side only) to know where to forward requests:
|
|
978
|
+
|
|
979
|
+
```env
|
|
980
|
+
# .env
|
|
981
|
+
# Default: https://api.azirid.com
|
|
982
|
+
# For local development:
|
|
983
|
+
AZIRID_API_URL=http://localhost:3000
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
> **Important:** Use `AZIRID_API_URL` (without `NEXT_PUBLIC_` prefix) — the API URL should never be exposed to the browser. The proxy runs server-side only.
|
|
987
|
+
|
|
988
|
+
### Next.js Config
|
|
989
|
+
|
|
990
|
+
#### Next.js 16+ (`next.config.ts`)
|
|
991
|
+
|
|
992
|
+
Turbopack is the default bundler — `transpilePackages` is no longer needed:
|
|
993
|
+
|
|
994
|
+
```ts
|
|
995
|
+
// next.config.ts
|
|
996
|
+
import type { NextConfig } from 'next'
|
|
997
|
+
|
|
998
|
+
const nextConfig: NextConfig = {}
|
|
999
|
+
|
|
1000
|
+
export default nextConfig
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
#### Next.js 14/15 (`next.config.js`)
|
|
1004
|
+
|
|
1005
|
+
```js
|
|
1006
|
+
// next.config.js
|
|
1007
|
+
const { withAziridProxy } = require('azirid-react/next')
|
|
1008
|
+
|
|
1009
|
+
/** @type {import('next').NextConfig} */
|
|
1010
|
+
module.exports = withAziridProxy()({
|
|
1011
|
+
transpilePackages: ['azirid-react'],
|
|
1012
|
+
})
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
### Route Protection (optional)
|
|
1016
|
+
|
|
1017
|
+
> **Not required for basic usage.** Only add this if you need to protect specific routes from unauthenticated users.
|
|
1018
|
+
|
|
1019
|
+
#### Next.js 16+ (`proxy.ts`)
|
|
1020
|
+
|
|
1021
|
+
```ts
|
|
1022
|
+
// proxy.ts — only needed if you want route protection
|
|
1023
|
+
import { createAziridProxy } from 'azirid-react/next'
|
|
1024
|
+
|
|
1025
|
+
export const proxy = createAziridProxy({
|
|
1026
|
+
protectedRoutes: ['/dashboard', '/settings'],
|
|
1027
|
+
loginUrl: '/login',
|
|
1028
|
+
publicRoutes: ['/login', '/signup', '/forgot-password'],
|
|
1029
|
+
})
|
|
1030
|
+
|
|
1031
|
+
export const config = {
|
|
1032
|
+
matcher: ['/((?!_next|favicon.ico|api/).*)'],
|
|
1033
|
+
}
|
|
1034
|
+
```
|
|
1035
|
+
|
|
1036
|
+
#### Next.js 14/15 (`middleware.ts`)
|
|
1037
|
+
|
|
1038
|
+
```ts
|
|
1039
|
+
// middleware.ts — only needed if you want route protection
|
|
1040
|
+
import { createAziridMiddleware } from 'azirid-react/next'
|
|
1041
|
+
|
|
1042
|
+
export default createAziridMiddleware({
|
|
1043
|
+
protectedRoutes: ['/dashboard', '/settings'],
|
|
1044
|
+
loginUrl: '/login',
|
|
1045
|
+
publicRoutes: ['/login', '/signup', '/forgot-password'],
|
|
1046
|
+
})
|
|
1047
|
+
|
|
1048
|
+
export const config = {
|
|
1049
|
+
matcher: ['/((?!_next|favicon.ico|api/).*)'],
|
|
1050
|
+
}
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
---
|
|
1054
|
+
|
|
1055
|
+
## Server-side (Next.js App Router)
|
|
1056
|
+
|
|
1057
|
+
For **Server Components**, **Server Actions**, and **Route Handlers**, use the `azirid-react/server` entry point to read the session token from the httpOnly `__session` cookie.
|
|
1058
|
+
|
|
1059
|
+
### Setup
|
|
1060
|
+
|
|
1061
|
+
```ts
|
|
1062
|
+
// lib/access-server.ts
|
|
1063
|
+
import { cookies } from 'next/headers'
|
|
1064
|
+
import { createServerAccess } from 'azirid-react/server'
|
|
1065
|
+
|
|
1066
|
+
// Works with all Next.js versions:
|
|
1067
|
+
// - Next.js 14: cookies() returns a sync cookie store
|
|
1068
|
+
// - Next.js 15/16+: cookies() returns a Promise — handled automatically
|
|
1069
|
+
export const { getSessionToken, getAccessToken } = createServerAccess({ cookies })
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
### Server Action example
|
|
1073
|
+
|
|
1074
|
+
```ts
|
|
1075
|
+
// app/actions/profile.ts
|
|
1076
|
+
'use server'
|
|
1077
|
+
import { getSessionToken } from '@/lib/access-server'
|
|
1078
|
+
|
|
1079
|
+
export async function getProfile() {
|
|
1080
|
+
const token = await getSessionToken()
|
|
1081
|
+
if (!token) throw new Error('Not authenticated')
|
|
1082
|
+
|
|
1083
|
+
const res = await fetch(`${process.env.AZIRID_API_URL}/v1/users/auth/me`, {
|
|
1084
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
1085
|
+
})
|
|
1086
|
+
return res.json()
|
|
1087
|
+
}
|
|
1088
|
+
```
|
|
1089
|
+
|
|
1090
|
+
### Server Component example
|
|
1091
|
+
|
|
1092
|
+
```tsx
|
|
1093
|
+
// app/dashboard/page.tsx
|
|
1094
|
+
import { redirect } from 'next/navigation'
|
|
1095
|
+
import { getSessionToken } from '@/lib/access-server'
|
|
1096
|
+
|
|
1097
|
+
export default async function DashboardPage() {
|
|
1098
|
+
const token = await getSessionToken()
|
|
1099
|
+
if (!token) redirect('/login')
|
|
1100
|
+
|
|
1101
|
+
const res = await fetch(`${process.env.AZIRID_API_URL}/v1/users/auth/me`, {
|
|
1102
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
1103
|
+
})
|
|
1104
|
+
const user = await res.json()
|
|
1105
|
+
|
|
1106
|
+
return <h1>Hello, {user.email}</h1>
|
|
1107
|
+
}
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
### Options
|
|
1111
|
+
|
|
1112
|
+
| Option | Type | Default | Description |
|
|
1113
|
+
| ------------ | -------- | ------------- | ------------------------------------------ |
|
|
1114
|
+
| `cookieName` | `string` | `"__session"` | Name of the httpOnly cookie with the token |
|
|
1115
|
+
|
|
1116
|
+
### `createSessionSyncHandler`
|
|
1117
|
+
|
|
1118
|
+
Creates a route handler that syncs the access token to a local httpOnly cookie. Useful for cross-origin development setups where the API is on a different domain.
|
|
1119
|
+
|
|
1120
|
+
```ts
|
|
1121
|
+
// app/api/auth/session/route.ts
|
|
1122
|
+
import { createSessionSyncHandler } from 'azirid-react/server'
|
|
1123
|
+
|
|
1124
|
+
export const { POST, DELETE } = createSessionSyncHandler()
|
|
1125
|
+
```
|
|
1126
|
+
|
|
1127
|
+
With custom options:
|
|
1128
|
+
|
|
1129
|
+
```ts
|
|
1130
|
+
export const { POST, DELETE } = createSessionSyncHandler({
|
|
1131
|
+
cookieName: '__session', // default
|
|
1132
|
+
secure: true, // set Secure flag on cookie
|
|
1133
|
+
maxAge: 3600, // cookie max age in seconds (default: 1h)
|
|
1134
|
+
})
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
| Option | Type | Default | Description |
|
|
1138
|
+
| ------------ | --------- | ------------- | ----------------------------------- |
|
|
1139
|
+
| `cookieName` | `string` | `"__session"` | Name of the httpOnly cookie |
|
|
1140
|
+
| `secure` | `boolean` | `false` | Set the `Secure` flag on the cookie |
|
|
1141
|
+
| `maxAge` | `number` | `3600` | Cookie max age in seconds |
|
|
1142
|
+
|
|
1143
|
+
---
|
|
1144
|
+
|
|
1145
|
+
## Version Compatibility
|
|
1146
|
+
|
|
1147
|
+
| Feature | Next.js 14 | Next.js 15 | Next.js 16+ |
|
|
1148
|
+
| ------------------- | ---------------- | ------------------- | ------------------- |
|
|
1149
|
+
| React | 18.x | 18.x / 19.x | 19.x+ |
|
|
1150
|
+
| Node.js | >= 18.0.0 | >= 18.17.0 | >= 20.9.0 |
|
|
1151
|
+
| Config file | `next.config.js` | `next.config.js/ts` | `next.config.ts` |
|
|
1152
|
+
| Request interceptor | `middleware.ts` | `middleware.ts` | `proxy.ts` |
|
|
1153
|
+
| `cookies()` | sync | async (with compat) | async only |
|
|
1154
|
+
| `params` | sync | async (with compat) | async only |
|
|
1155
|
+
| Bundler | Webpack | Webpack/Turbopack | Turbopack (default) |
|
|
1156
|
+
| `transpilePackages` | Required | Required | Not needed |
|
|
1157
|
+
|
|
1158
|
+
---
|
|
1159
|
+
|
|
1160
|
+
## Tailwind CSS (optional)
|
|
1161
|
+
|
|
1162
|
+
The built-in form components (`LoginForm`, `SignupForm`, etc.) use Tailwind utility classes. Add `azirid-react` to your `content` glob so Tailwind picks them up.
|
|
1163
|
+
|
|
1164
|
+
```js
|
|
1165
|
+
// tailwind.config.js
|
|
1166
|
+
module.exports = {
|
|
1167
|
+
content: ['./src/**/*.{ts,tsx}', './node_modules/azirid-react/dist/**/*.{js,mjs}'],
|
|
1168
|
+
}
|
|
1169
|
+
```
|
|
1170
|
+
|
|
1171
|
+
---
|
|
1172
|
+
|
|
1173
|
+
## Utilities
|
|
1174
|
+
|
|
1175
|
+
### `SDK_VERSION`
|
|
1176
|
+
|
|
1177
|
+
```tsx
|
|
1178
|
+
import { SDK_VERSION } from 'azirid-react'
|
|
1179
|
+
|
|
1180
|
+
console.log(`azirid-react v${SDK_VERSION}`)
|
|
1181
|
+
```
|
|
1182
|
+
|
|
1183
|
+
### `isAuthError`
|
|
1184
|
+
|
|
1185
|
+
Check if an error is an authentication error (401 or 403).
|
|
1186
|
+
|
|
1187
|
+
```tsx
|
|
1188
|
+
import { isAuthError } from 'azirid-react'
|
|
1189
|
+
|
|
1190
|
+
try {
|
|
1191
|
+
await someApiCall()
|
|
1192
|
+
} catch (err) {
|
|
1193
|
+
if (isAuthError(err)) {
|
|
1194
|
+
// redirect to login
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
```
|
|
1198
|
+
|
|
1199
|
+
### `removeStyles`
|
|
1200
|
+
|
|
1201
|
+
Remove CSS styles injected by SDK components. Useful when unmounting the provider.
|
|
1202
|
+
|
|
1203
|
+
```tsx
|
|
1204
|
+
import { removeStyles } from 'azirid-react'
|
|
1205
|
+
|
|
1206
|
+
useEffect(() => {
|
|
1207
|
+
return () => removeStyles()
|
|
1208
|
+
}, [])
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
### `cn`
|
|
1212
|
+
|
|
1213
|
+
Tailwind class merge utility (powered by `clsx` + `tailwind-merge`).
|
|
1214
|
+
|
|
1215
|
+
```tsx
|
|
1216
|
+
import { cn } from 'azirid-react'
|
|
1217
|
+
|
|
1218
|
+
<div className={cn('p-4 bg-white', isActive && 'bg-blue-500')} />
|
|
1219
|
+
```
|
|
1220
|
+
|
|
1221
|
+
### `BASE_PATHS` and `buildPaths`
|
|
1222
|
+
|
|
1223
|
+
```tsx
|
|
1224
|
+
import { BASE_PATHS, buildPaths } from 'azirid-react'
|
|
1225
|
+
|
|
1226
|
+
BASE_PATHS.proxy // '/api/auth'
|
|
1227
|
+
BASE_PATHS.direct // '/v1/users/auth'
|
|
1228
|
+
|
|
1229
|
+
// Build custom path map from a base path
|
|
1230
|
+
const paths = buildPaths('/custom/auth')
|
|
1231
|
+
// paths.login → '/custom/auth/login'
|
|
1232
|
+
// paths.signup → '/custom/auth/signup'
|
|
1233
|
+
// etc.
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
---
|
|
1237
|
+
|
|
1238
|
+
## Types Reference
|
|
1239
|
+
|
|
1240
|
+
All types are exported from `azirid-react` and can be imported directly:
|
|
1241
|
+
|
|
1242
|
+
```tsx
|
|
1243
|
+
import type {
|
|
1244
|
+
AuthUser,
|
|
1245
|
+
AuthSuccessResponse,
|
|
1246
|
+
AppBranding,
|
|
1247
|
+
SignupData,
|
|
1248
|
+
BillingPlan,
|
|
1249
|
+
UserSubscription,
|
|
1250
|
+
// ...
|
|
1251
|
+
} from 'azirid-react'
|
|
1252
|
+
```
|
|
1253
|
+
|
|
1254
|
+
### Auth Types
|
|
1255
|
+
|
|
1256
|
+
| Type | Description |
|
|
1257
|
+
| --- | --- |
|
|
1258
|
+
| `AuthUser` | Authenticated user object (`id`, `email`, `firstName`, `emailVerified`, `mfaEnabled`, `appId`, `tenantId`, ...) |
|
|
1259
|
+
| `AuthSuccessResponse` | Login/signup response (`accessToken`, `user`) |
|
|
1260
|
+
| `AuthState` | Full auth state (`user`, `accessToken`, `isAuthenticated`, `isLoading`, `error`) |
|
|
1261
|
+
| `SignupData` | Signup payload (`email`, `password`, `acceptedTosVersion?`, `referralCode?`, ...) |
|
|
1262
|
+
| `AppBranding` | Branding config (`displayName`, `logoUrl`, `primaryColor`, `removeBranding`, ...) |
|
|
1263
|
+
| `BootstrapResponse` | Bootstrap result (authenticated/unauthenticated + branding) |
|
|
1264
|
+
| `AziridProviderProps` | All provider props (see table above) |
|
|
1265
|
+
| `AziridContextValue` | Context value returned by `useAzirid()` |
|
|
1266
|
+
|
|
1267
|
+
### Billing Types
|
|
1268
|
+
|
|
1269
|
+
| Type | Description |
|
|
1270
|
+
| --- | --- |
|
|
1271
|
+
| `BillingPlan` | Plan object (`id`, `name`, `amount`, `currency`, `interval`, `features`, ...) |
|
|
1272
|
+
| `UserSubscription` | Subscription (`planId`, `plan`, `status`, `currentPeriodEnd`, `cancelAtPeriodEnd`, ...) |
|
|
1273
|
+
| `CheckoutResponse` | Checkout result (`url`, `sessionId`, `provider`, `plan`, `widgetConfig`, `bankDetails`, ...) |
|
|
1274
|
+
| `BillingInvoice` | Invoice (`amount`, `currency`, `status`, `paidAt`, `invoiceUrl`, ...) |
|
|
1275
|
+
| `PaymentProviderType` | `'STRIPE' \| 'PAYPAL' \| 'PAYPHONE' \| 'NUVEI' \| 'MANUAL_TRANSFER'` |
|
|
1276
|
+
| `AvailableProvider` | Provider info (`provider`, `checkout`, `subscriptions`) |
|
|
1277
|
+
| `SubmitTransferProofData` | Transfer proof payload (`planId`, `fileUrl`, `amount`, ...) |
|
|
1278
|
+
| `TransferProof` | Transfer proof object (`status`: `PENDING_REVIEW \| APPROVED \| REJECTED`, ...) |
|
|
1279
|
+
| `PayphoneWidgetConfig` | Payphone widget config (`token`, `storeId`, `amount`, ...) |
|
|
1280
|
+
|
|
1281
|
+
### Referral Types
|
|
1282
|
+
|
|
1283
|
+
| Type | Description |
|
|
1284
|
+
| --- | --- |
|
|
1285
|
+
| `ReferralInfo` | Referral info (`referralCode`, `referralUrl`, `totalReferred`, `completedReferrals`, ...) |
|
|
1286
|
+
| `ReferralStatsData` | Stats + referral list (`totalRewards`, `referrals[]`) |
|
|
1287
|
+
| `ReferralItem` | Single referral (`referredEmail`, `status`, `rewardStatus`, `rewardAmount`, ...) |
|
|
1288
|
+
|
|
1289
|
+
### Form Types
|
|
1290
|
+
|
|
1291
|
+
| Type | Description |
|
|
1292
|
+
| --- | --- |
|
|
1293
|
+
| `LoginFormProps` | LoginForm component props (`title`, `logo`, `labels`, `showSocialButtons`, ...) |
|
|
1294
|
+
| `SignupFormProps` | SignupForm component props |
|
|
1295
|
+
| `FieldError` | Validation error (`{ field: string, message: string }`) |
|
|
1296
|
+
| `UseFormReturn<T>` | Return type of `useFormState()` |
|
|
1297
|
+
|
|
1298
|
+
### Passkey Types
|
|
1299
|
+
|
|
1300
|
+
| Type | Description |
|
|
1301
|
+
| --- | --- |
|
|
1302
|
+
| `PasskeyItem` | Passkey entry (`id`, `deviceName`, `credentialId`, `lastUsedAt`) |
|
|
1303
|
+
| `PasskeyRegisterStartData` | Registration start payload (`deviceName?`) |
|
|
1304
|
+
| `PasskeyRegisterStartResponse` | Registration challenge (`challengeId`, `options`) |
|
|
1305
|
+
| `PasskeyLoginStartResponse` | Login challenge (`challengeId`, `options`) |
|
|
1306
|
+
|
|
1307
|
+
---
|
|
1308
|
+
|
|
1309
|
+
## License
|
|
1310
|
+
|
|
1311
|
+
MIT
|