@insforge/nextjs 0.4.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 ADDED
@@ -0,0 +1,759 @@
1
+ # @insforge/nextjs
2
+
3
+ Pre-built authentication UI components for Next.js applications using Insforge backend. This package provides a Clerk-like experience with drop-in components that connect directly to your Insforge backend, reducing token usage for AI agents.
4
+
5
+ ## Features
6
+
7
+ - **Drop-in UI Components**: Pre-styled SignIn, SignUp, and UserButton components
8
+ - **React Hooks**: Easy access to auth state with `useAuth()`, `useUser()`, `useSession()`
9
+ - **Control Components**: `<SignedIn>`, `<SignedOut>`, `<Protect>` for conditional rendering
10
+ - **Next.js Middleware**: Server-side route protection
11
+ - **OAuth Support**: Built-in Google and GitHub authentication
12
+ - **TypeScript**: Full type safety out of the box
13
+ - **Customizable**: Override styles with the `appearance` prop
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @insforge/nextjs lucide-react
19
+ # or
20
+ yarn add @insforge/nextjs lucide-react
21
+ ```
22
+
23
+ **Required peer dependencies:**
24
+ - `lucide-react` - Icon library (used for icons in components)
25
+
26
+ ## Quick Start
27
+
28
+ ### 1. Check Your Backend OAuth Configuration
29
+
30
+ First, query your Insforge backend to see what OAuth providers are configured:
31
+
32
+ ```bash
33
+ # Using curl (requires admin authentication)
34
+ curl -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
35
+ https://your-backend.insforge.app/api/metadata/auth
36
+ ```
37
+
38
+ **Response example:**
39
+ ```json
40
+ {
41
+ "oauths": [
42
+ { "provider": "github", "clientId": "...", ... },
43
+ { "provider": "google", "clientId": "...", ... }
44
+ ]
45
+ }
46
+ ```
47
+
48
+ **For AI Agents:** Use the Insforge MCP tool `get-backend-metadata` to automatically retrieve this configuration.
49
+
50
+ ### 2. Wrap your app with AuthProvider
51
+
52
+ ```tsx
53
+ // app/layout.tsx
54
+ import { AuthProvider } from '@insforge/nextjs';
55
+
56
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
57
+ const baseUrl = process.env.NEXT_PUBLIC_INSFORGE_BASE_URL!;
58
+
59
+ return (
60
+ <html lang="en">
61
+ <body>
62
+ <AuthProvider baseUrl={baseUrl}>
63
+ {children}
64
+ </AuthProvider>
65
+ </body>
66
+ </html>
67
+ );
68
+ }
69
+ ```
70
+
71
+ **Note:** Import the component styles:
72
+
73
+ ```tsx
74
+ // In your layout.tsx or _app.tsx
75
+ import '@insforge/nextjs/styles.css';
76
+ ```
77
+
78
+ **That's it!** No Tailwind configuration needed. The package now uses standalone CSS.
79
+
80
+ ### 3. Add OAuth callback page (Required for OAuth)
81
+
82
+ If you're using OAuth providers, you **must** create a callback page:
83
+
84
+ ```tsx
85
+ // app/auth/callback/page.tsx
86
+ 'use client';
87
+
88
+ import { useEffect, Suspense } from 'react';
89
+ import { useRouter, useSearchParams } from 'next/navigation';
90
+
91
+ function CallbackContent() {
92
+ const router = useRouter();
93
+ const searchParams = useSearchParams();
94
+
95
+ useEffect(() => {
96
+ const processOAuthCallback = () => {
97
+ const accessToken = searchParams.get('access_token');
98
+ const error = searchParams.get('error');
99
+
100
+ if (error) {
101
+ router.push('/sign-in?error=' + encodeURIComponent(error));
102
+ return;
103
+ }
104
+
105
+ if (accessToken) {
106
+ // Store token in localStorage (where SDK expects it)
107
+ localStorage.setItem('insforge-auth-token', accessToken);
108
+
109
+ // Get final destination
110
+ const finalDestination = sessionStorage.getItem('oauth_final_destination') || '/dashboard';
111
+ sessionStorage.removeItem('oauth_final_destination');
112
+
113
+ router.push(finalDestination);
114
+ } else {
115
+ router.push('/sign-in?error=no_token');
116
+ }
117
+ };
118
+
119
+ processOAuthCallback();
120
+ }, [searchParams, router]);
121
+
122
+ return (
123
+ <div className="flex items-center justify-center min-h-screen">
124
+ <div className="text-center">
125
+ <h2 className="text-2xl font-semibold mb-4">Completing authentication...</h2>
126
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
127
+ </div>
128
+ </div>
129
+ );
130
+ }
131
+
132
+ export default function OAuthCallbackPage() {
133
+ return (
134
+ <Suspense fallback={<div>Loading...</div>}>
135
+ <CallbackContent />
136
+ </Suspense>
137
+ );
138
+ }
139
+ ```
140
+
141
+ ### 4. Add sign-in page with OAuth providers
142
+
143
+ ```tsx
144
+ // app/sign-in/page.tsx
145
+ import { SignIn } from '@insforge/nextjs';
146
+
147
+ export default function SignInPage() {
148
+ return (
149
+ <SignIn
150
+ baseUrl={process.env.NEXT_PUBLIC_INSFORGE_BASE_URL!}
151
+ providers={['github', 'google']} // Based on your backend config
152
+ afterSignInUrl="/dashboard"
153
+ title="Welcome to MyApp"
154
+ subtitle="Sign in to continue"
155
+ />
156
+ );
157
+ }
158
+ ```
159
+
160
+ **Note:** The `providers` prop should match what you've configured in your Insforge backend. OAuth buttons will only render for providers specified in this array.
161
+
162
+ ### 5. Add sign-up page
163
+
164
+ ```tsx
165
+ // app/sign-up/page.tsx
166
+ import { SignUp } from '@insforge/nextjs';
167
+
168
+ export default function SignUpPage() {
169
+ return (
170
+ <SignUp
171
+ baseUrl={process.env.NEXT_PUBLIC_INSFORGE_BASE_URL!}
172
+ providers={['github', 'google']} // Based on your backend config
173
+ afterSignUpUrl="/dashboard"
174
+ />
175
+ );
176
+ }
177
+ ```
178
+
179
+ ### 6. Use conditional components
180
+
181
+ ```tsx
182
+ // app/page.tsx
183
+ import { SignedIn, SignedOut, UserButton } from '@insforge/nextjs';
184
+
185
+ export default function Home() {
186
+ return (
187
+ <div>
188
+ <nav>
189
+ <SignedOut>
190
+ <a href="/sign-in">Sign In</a>
191
+ </SignedOut>
192
+
193
+ <SignedIn>
194
+ <UserButton afterSignOutUrl="/" />
195
+ </SignedIn>
196
+ </nav>
197
+
198
+ <SignedIn>
199
+ <h1>Welcome back!</h1>
200
+ </SignedIn>
201
+
202
+ <SignedOut>
203
+ <h1>Please sign in</h1>
204
+ </SignedOut>
205
+ </div>
206
+ );
207
+ }
208
+ ```
209
+
210
+ ### 7. Protect routes with middleware
211
+
212
+ ```ts
213
+ // middleware.ts
214
+ import { withAuth } from '@insforge/nextjs/middleware';
215
+
216
+ export default withAuth({
217
+ baseUrl: process.env.INSFORGE_BASE_URL!,
218
+ publicRoutes: ['/sign-in', '/sign-up', '/'],
219
+ signInUrl: '/sign-in',
220
+ });
221
+
222
+ export const config = {
223
+ matcher: ['/((?!_next|api|.*\\..*).*)'],
224
+ };
225
+ ```
226
+
227
+ ## API Reference
228
+
229
+ ### Components
230
+
231
+ #### `<AuthProvider>`
232
+
233
+ Wraps your application and provides auth context.
234
+
235
+ ```tsx
236
+ <AuthProvider
237
+ baseUrl="https://your-backend.insforge.app"
238
+ onAuthChange={(user) => console.log('Auth changed:', user)}
239
+ >
240
+ {children}
241
+ </AuthProvider>
242
+ ```
243
+
244
+ **Props:**
245
+ - `baseUrl` (required): Your Insforge backend URL
246
+ - `onAuthChange`: Optional callback when auth state changes
247
+
248
+ ---
249
+
250
+ #### `<SignIn>`
251
+
252
+ Pre-built sign-in form with email/password and OAuth.
253
+
254
+ ```tsx
255
+ <SignIn
256
+ baseUrl="https://your-backend.insforge.app"
257
+ providers={['github', 'google']}
258
+ afterSignInUrl="/dashboard"
259
+ appearance={{
260
+ container: { background: '#f5f5f5' },
261
+ button: { background: 'blue' }
262
+ }}
263
+ onSuccess={(user) => console.log('Signed in:', user)}
264
+ onError={(error) => console.error('Error:', error)}
265
+ />
266
+ ```
267
+
268
+ **Props:**
269
+ - `baseUrl` (required): Your Insforge backend URL
270
+ - `providers`: Array of OAuth providers to display (e.g., `['github', 'google']`). Omit or pass empty array for email/password only
271
+ - `afterSignInUrl`: Redirect URL after successful sign-in (default: `/`)
272
+ - `appearance`: Custom styles for container, form, and button
273
+ - `onSuccess`: Callback on successful sign-in
274
+ - `onError`: Callback on error
275
+
276
+ ---
277
+
278
+ #### `<SignUp>`
279
+
280
+ Pre-built sign-up form with email/password and OAuth.
281
+
282
+ ```tsx
283
+ <SignUp
284
+ baseUrl="https://your-backend.insforge.app"
285
+ providers={['github', 'google']}
286
+ afterSignUpUrl="/onboarding"
287
+ appearance={{
288
+ container: { background: '#f5f5f5' }
289
+ }}
290
+ />
291
+ ```
292
+
293
+ **Props:**
294
+ - `baseUrl` (required): Your Insforge backend URL
295
+ - `providers`: Array of OAuth providers to display (e.g., `['github', 'google']`). Omit or pass empty array for email/password only
296
+ - `afterSignUpUrl`: Redirect URL after successful sign-up (default: `/`)
297
+ - `appearance`: Custom styles for container, form, and button
298
+ - `onSuccess`: Callback on successful sign-up
299
+ - `onError`: Callback on error
300
+
301
+ ---
302
+
303
+ #### `<UserButton>`
304
+
305
+ User profile dropdown with sign-out functionality.
306
+
307
+ ```tsx
308
+ <UserButton
309
+ afterSignOutUrl="/"
310
+ showEmail={true}
311
+ appearance={{
312
+ button: { borderRadius: '50%' },
313
+ dropdown: { minWidth: '250px' }
314
+ }}
315
+ />
316
+ ```
317
+
318
+ **Props:**
319
+ - `afterSignOutUrl`: Redirect URL after sign-out (default: `/`)
320
+ - `showEmail`: Show user email in dropdown (default: `true`)
321
+ - `appearance`: Custom styles for button and dropdown
322
+
323
+ ---
324
+
325
+ #### `<SignedIn>`
326
+
327
+ Renders children only when user is authenticated.
328
+
329
+ ```tsx
330
+ <SignedIn>
331
+ <Dashboard />
332
+ </SignedIn>
333
+ ```
334
+
335
+ ---
336
+
337
+ #### `<SignedOut>`
338
+
339
+ Renders children only when user is NOT authenticated.
340
+
341
+ ```tsx
342
+ <SignedOut>
343
+ <LandingPage />
344
+ </SignedOut>
345
+ ```
346
+
347
+ ---
348
+
349
+ #### `<Protect>`
350
+
351
+ Protects content and optionally redirects unauthenticated users.
352
+
353
+ ```tsx
354
+ <Protect
355
+ fallback={<Loading />}
356
+ redirectTo="/sign-in"
357
+ condition={(user) => user.role === 'admin'}
358
+ >
359
+ <AdminPanel />
360
+ </Protect>
361
+ ```
362
+
363
+ **Props:**
364
+ - `fallback`: Component to show while loading or if unauthorized
365
+ - `redirectTo`: URL to redirect to if not authorized (default: `/sign-in`)
366
+ - `condition`: Optional function for custom authorization logic (e.g., role-based access)
367
+
368
+ ---
369
+
370
+ ### Hooks
371
+
372
+ #### `useAuth()`
373
+
374
+ Access complete auth state and methods.
375
+
376
+ ```tsx
377
+ import { useAuth } from '@insforge/nextjs';
378
+
379
+ function Component() {
380
+ const {
381
+ user,
382
+ session,
383
+ isLoaded,
384
+ isSignedIn,
385
+ signIn,
386
+ signUp,
387
+ signOut,
388
+ updateUser
389
+ } = useAuth();
390
+
391
+ if (!isLoaded) return <div>Loading...</div>;
392
+ if (!isSignedIn) return <div>Please sign in</div>;
393
+
394
+ return <div>Hello {user.email}</div>;
395
+ }
396
+ ```
397
+
398
+ **Returns:**
399
+ - `user`: Current user object or `null`
400
+ - `session`: Current session object or `null`
401
+ - `isLoaded`: Whether auth state has loaded
402
+ - `isSignedIn`: Whether user is authenticated
403
+ - `signIn(email, password)`: Sign in method
404
+ - `signUp(email, password)`: Sign up method
405
+ - `signOut()`: Sign out method
406
+ - `updateUser(data)`: Update user data
407
+
408
+ ---
409
+
410
+ #### `useUser()`
411
+
412
+ Access current user data.
413
+
414
+ ```tsx
415
+ import { useUser } from '@insforge/nextjs';
416
+
417
+ function Component() {
418
+ const { user, isLoaded } = useUser();
419
+
420
+ if (!isLoaded) return <div>Loading...</div>;
421
+ if (!user) return <div>Not signed in</div>;
422
+
423
+ return <div>{user.email}</div>;
424
+ }
425
+ ```
426
+
427
+ ---
428
+
429
+ #### `useSession()`
430
+
431
+ Access current session data.
432
+
433
+ ```tsx
434
+ import { useSession } from '@insforge/nextjs';
435
+
436
+ function Component() {
437
+ const { session, isLoaded, isSignedIn } = useSession();
438
+
439
+ return <div>Signed in: {isSignedIn ? 'Yes' : 'No'}</div>;
440
+ }
441
+ ```
442
+
443
+ ---
444
+
445
+ ### Middleware
446
+
447
+ #### `withAuth(config)`
448
+
449
+ Create Next.js middleware for route protection.
450
+
451
+ ```ts
452
+ import { withAuth } from '@insforge/nextjs/middleware';
453
+
454
+ export default withAuth({
455
+ baseUrl: process.env.INSFORGE_BASE_URL!,
456
+ publicRoutes: ['/sign-in', '/sign-up', '/', '/about'],
457
+ signInUrl: '/sign-in',
458
+ cookieName: 'insforge_token',
459
+ });
460
+ ```
461
+
462
+ **Config:**
463
+ - `baseUrl` (required): Your Insforge backend URL
464
+ - `publicRoutes`: Array of routes that don't require auth (supports wildcards: `/blog/*`)
465
+ - `signInUrl`: URL to redirect to when not authenticated (default: `/sign-in`)
466
+ - `cookieName`: Cookie name for auth token (default: `insforge_token`)
467
+
468
+ ---
469
+
470
+ #### `getAuthUser(headers)` and `getAuthToken(headers)`
471
+
472
+ Get authenticated user ID or token in server components.
473
+
474
+ ```tsx
475
+ import { headers } from 'next/headers';
476
+ import { getAuthUser, getAuthToken } from '@insforge/nextjs/middleware';
477
+
478
+ export default async function ServerComponent() {
479
+ const userId = getAuthUser(headers());
480
+ const token = getAuthToken(headers());
481
+
482
+ // Fetch user-specific data
483
+ const data = await fetchUserData(userId, token);
484
+
485
+ return <div>User ID: {userId}</div>;
486
+ }
487
+ ```
488
+
489
+ ---
490
+
491
+ ## Styling
492
+
493
+ This package uses **standalone CSS** for styling - no Tailwind CSS dependency required! All components come pre-styled and ready to use.
494
+
495
+ ### Setup
496
+
497
+ Simply import the CSS file once in your root layout:
498
+
499
+ ```tsx
500
+ // app/layout.tsx
501
+ import '@insforge/nextjs/styles.css';
502
+ ```
503
+
504
+ That's it! No configuration needed.
505
+
506
+ ### Typography
507
+
508
+ The package includes the **Manrope variable font** for a modern, professional look. The font is automatically loaded and available as a CSS variable:
509
+
510
+ ```css
511
+ /* Use the font in your own CSS */
512
+ .my-component {
513
+ font-family: var(--font-manrope);
514
+ }
515
+ ```
516
+
517
+ The font family includes fallbacks to system fonts for optimal loading:
518
+ - `--font-manrope`: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif
519
+
520
+ ### Customization
521
+
522
+ **Option 1: Text Customization** (Easiest)
523
+
524
+ All text elements in the components can be customized via props:
525
+
526
+ ```tsx
527
+ <SignIn
528
+ baseUrl="..."
529
+ // Customize all text
530
+ title="Welcome Back to MyApp"
531
+ subtitle="We're happy to see you again"
532
+ emailLabel="Your Email"
533
+ emailPlaceholder="you@company.com"
534
+ passwordLabel="Your Password"
535
+ forgotPasswordText="Forgot it?"
536
+ submitButtonText="Log In"
537
+ loadingButtonText="Logging in..."
538
+ signUpText="New user?"
539
+ signUpLinkText="Create an account"
540
+ signUpUrl="/register"
541
+ dividerText="or continue with"
542
+ />
543
+
544
+ <SignUp
545
+ baseUrl="..."
546
+ // Customize signup text
547
+ title="Join MyApp Today"
548
+ subtitle="Create your account in seconds"
549
+ submitButtonText="Create Account"
550
+ signInText="Existing user?"
551
+ signInLinkText="Log in here"
552
+ />
553
+ ```
554
+
555
+ **Option 2: Style Customization**
556
+
557
+ Override inline styles for specific components:
558
+
559
+ ```tsx
560
+ <SignIn
561
+ appearance={{
562
+ container: {
563
+ background: 'linear-gradient(to right, #4f46e5, #7c3aed)'
564
+ },
565
+ form: {
566
+ padding: '3rem',
567
+ borderRadius: '16px'
568
+ },
569
+ button: {
570
+ background: '#4f46e5',
571
+ color: 'white'
572
+ }
573
+ }}
574
+ />
575
+ ```
576
+
577
+ **Option 3: Override CSS Classes**
578
+
579
+ All components use semantic CSS class names prefixed with `insforge-`:
580
+
581
+ ```css
582
+ /* In your global CSS file */
583
+
584
+ /* Customize the primary button */
585
+ .insforge-btn-primary {
586
+ background: #8b5cf6;
587
+ border-radius: 12px;
588
+ }
589
+
590
+ /* Customize form inputs */
591
+ .insforge-input {
592
+ border-color: #e0e0e0;
593
+ border-radius: 8px;
594
+ }
595
+
596
+ /* Customize the auth card */
597
+ .insforge-auth-card {
598
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.15);
599
+ }
600
+ ```
601
+
602
+ **Available CSS Classes:**
603
+ - `.insforge-auth-container` - Main container
604
+ - `.insforge-auth-card` - Form card
605
+ - `.insforge-input` - Input fields
606
+ - `.insforge-btn-primary` - Primary buttons
607
+ - `.insforge-oauth-btn` - OAuth buttons
608
+ - `.insforge-user-button` - User menu button
609
+ - `.insforge-error-banner` - Error messages
610
+
611
+ ---
612
+
613
+ ## Advanced Usage
614
+
615
+ ### Role-Based Access Control
616
+
617
+ ```tsx
618
+ <Protect condition={(user) => user.role === 'admin'}>
619
+ <AdminDashboard />
620
+ </Protect>
621
+ ```
622
+
623
+ ### Custom Auth Callbacks
624
+
625
+ ```tsx
626
+ <AuthProvider
627
+ baseUrl={baseUrl}
628
+ onAuthChange={(user) => {
629
+ if (user) {
630
+ analytics.identify(user.id);
631
+ } else {
632
+ analytics.reset();
633
+ }
634
+ }}
635
+ >
636
+ {children}
637
+ </AuthProvider>
638
+ ```
639
+
640
+ ### Server-Side User Data
641
+
642
+ ```tsx
643
+ // app/dashboard/page.tsx
644
+ import { headers } from 'next/headers';
645
+ import { getAuthUser } from '@insforge/nextjs/middleware';
646
+ import { createClient } from '@insforge/sdk';
647
+
648
+ export default async function Dashboard() {
649
+ const userId = getAuthUser(headers());
650
+
651
+ const insforge = createClient({
652
+ baseUrl: process.env.INSFORGE_BASE_URL!
653
+ });
654
+
655
+ const user = await insforge.auth.getUserById(userId);
656
+
657
+ return <div>Welcome {user.email}</div>;
658
+ }
659
+ ```
660
+
661
+ ---
662
+
663
+ ## Environment Variables
664
+
665
+ Create a `.env.local` file:
666
+
667
+ ```env
668
+ # Public (accessible in browser)
669
+ NEXT_PUBLIC_INSFORGE_BASE_URL=https://your-backend.insforge.app
670
+
671
+ # Server-only (middleware)
672
+ INSFORGE_BASE_URL=https://your-backend.insforge.app
673
+ ```
674
+
675
+ ---
676
+
677
+ ## Why @insforge/nextjs?
678
+
679
+ ### For Developers
680
+ - **Faster Development**: Drop-in components save hours of auth implementation
681
+ - **Best Practices**: Built-in security, session management, and error handling
682
+ - **Flexible**: Fully customizable while providing sensible defaults
683
+ - **Explicit Configuration**: OAuth providers passed as props - no hidden API calls or magic
684
+
685
+ ### For AI Agents
686
+ - **Reduced Token Usage**: Pre-built components mean agents don't rebuild UI each time
687
+ - **Consistent UX**: Standard patterns across projects
688
+ - **Less Context**: Import and use instead of implementing from scratch
689
+ - **Discoverable**: Query `/api/metadata/auth` via MCP to get OAuth config, then generate code with correct `providers` prop
690
+
691
+ ### AI Agent Workflow with Insforge MCP
692
+
693
+ When building Next.js apps with authentication:
694
+
695
+ 1. **Connect to Insforge MCP** - Access the user's Insforge backend
696
+ 2. **Query metadata** - Call `get-backend-metadata` to get OAuth configuration
697
+ 3. **Generate code** - Write auth pages with explicit `providers={['github', 'google']}` based on backend config
698
+ 4. **User sees OAuth buttons** - Components render buttons for configured providers
699
+
700
+ **Example MCP Response:**
701
+ ```json
702
+ {
703
+ "auth": {
704
+ "oauths": [
705
+ { "provider": "github", "clientId": "..." },
706
+ { "provider": "google", "clientId": "..." }
707
+ ]
708
+ }
709
+ }
710
+ ```
711
+
712
+ **Agent generates:**
713
+ ```tsx
714
+ <SignIn
715
+ baseUrl={process.env.NEXT_PUBLIC_INSFORGE_BASE_URL!}
716
+ providers={['github', 'google']} // From metadata
717
+ afterSignInUrl="/dashboard"
718
+ />
719
+ ```
720
+
721
+ ---
722
+
723
+ ## TypeScript
724
+
725
+ Full TypeScript support with exported types:
726
+
727
+ ```tsx
728
+ import type {
729
+ InsforgeUser,
730
+ InsforgeSession,
731
+ AuthContextValue,
732
+ SignInProps,
733
+ ProtectProps
734
+ } from '@insforge/nextjs';
735
+ ```
736
+
737
+ ---
738
+
739
+ ## Examples
740
+
741
+ Check out the `/examples` directory for:
742
+ - Basic Next.js App Router setup
743
+ - Role-based access control
744
+ - Custom styling
745
+ - Server-side data fetching
746
+
747
+ ---
748
+
749
+ ## License
750
+
751
+ MIT
752
+
753
+ ---
754
+
755
+ ## Support
756
+
757
+ - Documentation: https://docs.insforge.app
758
+ - Issues: https://github.com/insforge/nextjs/issues
759
+ - Discord: https://discord.gg/insforge