@insforge/react 0.1.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,849 @@
1
+ # @insforge/react
2
+
3
+ **Complete authentication solution for React applications.** Framework-agnostic components with full business logic included.
4
+
5
+ ## Why @insforge/react?
6
+
7
+ ✅ **Complete Package** - Not just UI, includes SDK integration, providers, and hooks
8
+ ✅ **Framework Agnostic** - Works with Next.js, Vite, Remix, or any React framework
9
+ ✅ **5-Minute Setup** - Provider + Components = done
10
+ ✅ **Full TypeScript** - Complete type safety out of the box
11
+ ✅ **Customizable** - Every component supports appearance props and text customization
12
+
13
+ **Need just UI?** All components are exported separately for maximum flexibility.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @insforge/react
19
+ # or
20
+ yarn add @insforge/react
21
+ # or
22
+ pnpm add @insforge/react
23
+ ```
24
+
25
+ Dependencies automatically include `@insforge/sdk` for authentication logic.
26
+
27
+ ---
28
+
29
+ ## Quick Start
30
+
31
+ ### 1. Setup Provider
32
+
33
+ Wrap your app with `InsforgeProvider` in the root:
34
+
35
+ ```tsx
36
+ // React / Vite
37
+ import { InsforgeProvider } from '@insforge/react';
38
+ import '@insforge/react/styles.css';
39
+
40
+ function App() {
41
+ return (
42
+ <InsforgeProvider baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}>
43
+ {/* Your app */}
44
+ </InsforgeProvider>
45
+ );
46
+ }
47
+ ```
48
+
49
+ ```tsx
50
+ // Next.js App Router
51
+ 'use client';
52
+ import { InsforgeProvider } from '@insforge/react';
53
+ import '@insforge/react/styles.css';
54
+
55
+ export default function RootLayout({ children }) {
56
+ return (
57
+ <html>
58
+ <body>
59
+ <InsforgeProvider baseUrl={process.env.NEXT_PUBLIC_INSFORGE_BASE_URL}>
60
+ {children}
61
+ </InsforgeProvider>
62
+ </body>
63
+ </html>
64
+ );
65
+ }
66
+ ```
67
+
68
+ **Props:**
69
+ - `baseUrl` (required): Your Insforge backend URL
70
+ - `onAuthChange` (optional): Callback when auth state changes
71
+ - `syncTokenToCookie` (optional): Custom function to sync token to cookies (for Next.js SSR)
72
+ - `clearCookie` (optional): Custom function to clear cookie on sign out
73
+
74
+ ### 2. Use Pre-built Components
75
+
76
+ The easiest way to add authentication:
77
+
78
+ ```tsx
79
+ 'use client'; // for Next.js
80
+ import { SignIn, SignUp } from '@insforge/react';
81
+
82
+ // Sign In Page
83
+ export default function SignInPage() {
84
+ return (
85
+ <div className="min-h-screen flex items-center justify-center bg-gray-50">
86
+ <SignIn
87
+ afterSignInUrl="/dashboard"
88
+ signUpUrl="/sign-up"
89
+ />
90
+ </div>
91
+ );
92
+ }
93
+
94
+ // Sign Up Page
95
+ export default function SignUpPage() {
96
+ return (
97
+ <div className="min-h-screen flex items-center justify-center bg-gray-50">
98
+ <SignUp
99
+ afterSignUpUrl="/dashboard"
100
+ signInUrl="/sign-in"
101
+ />
102
+ </div>
103
+ );
104
+ }
105
+ ```
106
+
107
+ ### 3. Create Callback Page (for OAuth)
108
+
109
+ Handle OAuth redirects:
110
+
111
+ ```tsx
112
+ 'use client'; // for Next.js
113
+ import { InsforgeCallback } from '@insforge/react';
114
+
115
+ export default function CallbackPage() {
116
+ return <InsforgeCallback />;
117
+ }
118
+ ```
119
+
120
+ ### 4. Use Hooks & Components
121
+
122
+ ```tsx
123
+ 'use client'; // for Next.js
124
+ import { SignedIn, SignedOut, UserButton, useAuth, useUser } from '@insforge/react';
125
+
126
+ export default function Home() {
127
+ const { isSignedIn } = useAuth();
128
+ const { user } = useUser();
129
+
130
+ return (
131
+ <div>
132
+ <SignedOut>
133
+ <a href="/sign-in">Sign In</a>
134
+ </SignedOut>
135
+
136
+ <SignedIn>
137
+ <UserButton afterSignOutUrl="/" />
138
+ <h1>Welcome, {user?.email}!</h1>
139
+ </SignedIn>
140
+ </div>
141
+ );
142
+ }
143
+ ```
144
+
145
+ **That's it!** 🎉 Your React app now has production-ready authentication.
146
+
147
+ ---
148
+
149
+ ## How It Works
150
+
151
+ ```
152
+ 1. User visits Sign In page → Renders <SignIn> component
153
+
154
+ 2. User enters credentials → Component calls SDK methods
155
+
156
+ 3. SDK communicates with backend → Returns auth token
157
+
158
+ 4. Provider updates auth state → Components re-render
159
+
160
+ 5. User sees authenticated UI → Redirect to dashboard
161
+ ```
162
+
163
+ **Architecture:**
164
+ - **InsforgeProvider**: Manages authentication state globally
165
+ - **SDK Integration**: All auth operations go through `@insforge/sdk`
166
+ - **React Context**: Provides auth state to all child components
167
+ - **Hooks**: Easy access to auth methods and user data
168
+
169
+ ---
170
+
171
+ ## Complete Components (with Business Logic)
172
+
173
+ These components include full authentication logic:
174
+
175
+ ### `<SignIn />`
176
+
177
+ Complete sign-in component with email/password and OAuth:
178
+
179
+ ```tsx
180
+ import { SignIn } from '@insforge/react';
181
+ import { useNavigate } from 'react-router-dom';
182
+
183
+ function SignInPage() {
184
+ const navigate = useNavigate();
185
+
186
+ return (
187
+ <SignIn
188
+ afterSignInUrl="/dashboard"
189
+ signUpUrl="/sign-up"
190
+ forgotPasswordUrl="/forgot-password"
191
+ onSuccess={(user, accessToken) => {
192
+ console.log('Signed in:', user);
193
+ }}
194
+ onError={(error) => {
195
+ console.error('Error:', error);
196
+ }}
197
+ onRedirect={(url) => navigate(url)}
198
+ // Customization
199
+ title="Welcome Back"
200
+ subtitle="Sign in to continue"
201
+ appearance={{
202
+ containerClassName: "shadow-xl",
203
+ buttonClassName: "bg-blue-600"
204
+ }}
205
+ />
206
+ );
207
+ }
208
+ ```
209
+
210
+ **Key Features:**
211
+ - Email/password authentication
212
+ - OAuth provider buttons (auto-detected from backend)
213
+ - Password visibility toggle
214
+ - Error handling
215
+ - Loading states
216
+ - Customizable text and styling
217
+
218
+ ### `<SignUp />`
219
+
220
+ Complete sign-up component with password strength validation:
221
+
222
+ ```tsx
223
+ import { SignUp } from '@insforge/react';
224
+
225
+ function SignUpPage() {
226
+ return (
227
+ <SignUp
228
+ afterSignUpUrl="/onboarding"
229
+ signInUrl="/sign-in"
230
+ onSuccess={(user, accessToken) => {
231
+ // Track sign-up event
232
+ analytics.track('user_signed_up', { userId: user.id });
233
+ }}
234
+ />
235
+ );
236
+ }
237
+ ```
238
+
239
+ **Key Features:**
240
+ - Email/password registration
241
+ - Real-time password strength indicator
242
+ - OAuth provider buttons
243
+ - Form validation
244
+ - Customizable requirements
245
+
246
+ ### `<UserButton />`
247
+
248
+ User profile dropdown with sign-out:
249
+
250
+ ```tsx
251
+ import { UserButton } from '@insforge/react';
252
+
253
+ function Header() {
254
+ return (
255
+ <header>
256
+ <nav>
257
+ {/* Your navigation */}
258
+ </nav>
259
+ <UserButton
260
+ afterSignOutUrl="/"
261
+ mode="detailed" // or "simple"
262
+ appearance={{
263
+ buttonClassName: "hover:bg-gray-100",
264
+ nameClassName: "text-gray-900",
265
+ emailClassName: "text-gray-600"
266
+ }}
267
+ />
268
+ </header>
269
+ );
270
+ }
271
+ ```
272
+
273
+ **Modes:**
274
+ - `detailed`: Shows avatar + name + email
275
+ - `simple`: Shows avatar only
276
+
277
+ ### `<Protect />`
278
+
279
+ Protected content with conditional rendering:
280
+
281
+ ```tsx
282
+ import { Protect } from '@insforge/react';
283
+
284
+ function Dashboard() {
285
+ return (
286
+ <div>
287
+ <h1>Dashboard</h1>
288
+
289
+ {/* Simple protection */}
290
+ <Protect redirectTo="/sign-in">
291
+ <UserContent />
292
+ </Protect>
293
+
294
+ {/* Role-based protection */}
295
+ <Protect
296
+ redirectTo="/unauthorized"
297
+ condition={(user) => user.role === 'admin'}
298
+ >
299
+ <AdminPanel />
300
+ </Protect>
301
+ </div>
302
+ );
303
+ }
304
+ ```
305
+
306
+ ### `<SignedIn>` / `<SignedOut>`
307
+
308
+ Conditional rendering based on auth state:
309
+
310
+ ```tsx
311
+ import { SignedIn, SignedOut } from '@insforge/react';
312
+
313
+ function NavBar() {
314
+ return (
315
+ <nav>
316
+ <SignedOut>
317
+ <a href="/sign-in">Sign In</a>
318
+ <a href="/sign-up">Sign Up</a>
319
+ </SignedOut>
320
+
321
+ <SignedIn>
322
+ <a href="/dashboard">Dashboard</a>
323
+ <UserButton />
324
+ </SignedIn>
325
+ </nav>
326
+ );
327
+ }
328
+ ```
329
+
330
+ ### `<InsforgeCallback />`
331
+
332
+ OAuth callback handler (3 lines instead of 70+):
333
+
334
+ ```tsx
335
+ 'use client';
336
+ import { InsforgeCallback } from '@insforge/react';
337
+
338
+ export default function CallbackPage() {
339
+ return <InsforgeCallback redirectTo="/dashboard" />;
340
+ }
341
+ ```
342
+
343
+ ---
344
+
345
+ ## Hooks
346
+
347
+ ### `useAuth()`
348
+
349
+ Access authentication methods:
350
+
351
+ ```tsx
352
+ import { useAuth } from '@insforge/react';
353
+
354
+ function LoginButton() {
355
+ const { signIn, signUp, signOut, isSignedIn, isLoaded } = useAuth();
356
+
357
+ const handleSignIn = async () => {
358
+ try {
359
+ await signIn('user@example.com', 'password');
360
+ // Redirect or update UI
361
+ } catch (error) {
362
+ console.error('Sign in failed:', error);
363
+ }
364
+ };
365
+
366
+ if (!isLoaded) return <div>Loading...</div>;
367
+
368
+ return (
369
+ <button onClick={isSignedIn ? signOut : handleSignIn}>
370
+ {isSignedIn ? 'Sign Out' : 'Sign In'}
371
+ </button>
372
+ );
373
+ }
374
+ ```
375
+
376
+ **Returns:**
377
+ - `signIn(email, password)` - Sign in with email/password
378
+ - `signUp(email, password)` - Sign up new user
379
+ - `signOut()` - Sign out current user
380
+ - `isSignedIn` - Boolean auth state
381
+ - `isLoaded` - Boolean loading state
382
+
383
+ ### `useUser()`
384
+
385
+ Access user data:
386
+
387
+ ```tsx
388
+ import { useUser } from '@insforge/react';
389
+
390
+ function UserProfile() {
391
+ const { user, isLoaded, updateUser } = useUser();
392
+
393
+ if (!isLoaded) return <div>Loading...</div>;
394
+ if (!user) return <div>Not signed in</div>;
395
+
396
+ const handleUpdate = async () => {
397
+ await updateUser({ name: 'New Name' });
398
+ };
399
+
400
+ return (
401
+ <div>
402
+ <p>Email: {user.email}</p>
403
+ <p>Name: {user.name}</p>
404
+ <img src={user.avatarUrl} alt="Avatar" />
405
+ <button onClick={handleUpdate}>Update Name</button>
406
+ </div>
407
+ );
408
+ }
409
+ ```
410
+
411
+ **Returns:**
412
+ - `user` - User object with id, email, name, avatarUrl
413
+ - `isLoaded` - Boolean loading state
414
+ - `updateUser(data)` - Update user profile
415
+ - `setUser(user)` - Manually set user state
416
+
417
+ ### `usePublicAuthConfig()`
418
+
419
+ Get OAuth providers and password requirements:
420
+
421
+ ```tsx
422
+ import { usePublicAuthConfig } from '@insforge/react';
423
+
424
+ function SignInPage() {
425
+ const { oauthProviders, emailConfig, isLoaded } = usePublicAuthConfig();
426
+
427
+ if (!isLoaded) return <div>Loading...</div>;
428
+
429
+ return (
430
+ <div>
431
+ <p>Available OAuth: {oauthProviders.join(', ')}</p>
432
+ <p>Password min length: {emailConfig?.passwordMinLength}</p>
433
+ </div>
434
+ );
435
+ }
436
+ ```
437
+
438
+ **⚠️ Important:** Only use this hook in SignIn/SignUp components to avoid unnecessary API calls.
439
+
440
+ ---
441
+
442
+ ## UI Form Components (Pure UI)
443
+
444
+ Build custom auth flows with pre-built forms:
445
+
446
+ ### `<SignInForm />`
447
+
448
+ ```tsx
449
+ import { SignInForm } from '@insforge/react';
450
+ import { useState } from 'react';
451
+
452
+ function CustomSignIn() {
453
+ const [email, setEmail] = useState('');
454
+ const [password, setPassword] = useState('');
455
+ const [error, setError] = useState('');
456
+ const [loading, setLoading] = useState(false);
457
+
458
+ const handleSubmit = async (e) => {
459
+ e.preventDefault();
460
+ setLoading(true);
461
+ // Your auth logic
462
+ };
463
+
464
+ return (
465
+ <SignInForm
466
+ email={email}
467
+ password={password}
468
+ onEmailChange={setEmail}
469
+ onPasswordChange={setPassword}
470
+ onSubmit={handleSubmit}
471
+ error={error}
472
+ loading={loading}
473
+ availableProviders={['google', 'github']}
474
+ onOAuthClick={(provider) => handleOAuth(provider)}
475
+ />
476
+ );
477
+ }
478
+ ```
479
+
480
+ **Other Form Components:**
481
+ - `<SignUpForm />` - Sign up with password strength
482
+ - `<ForgotPasswordForm />` - Request password reset
483
+ - `<ResetPasswordForm />` - Reset password with token
484
+ - `<VerifyEmailStatus />` - Email verification status
485
+
486
+ ---
487
+
488
+ ## Atomic Components (Maximum Flexibility)
489
+
490
+ Build completely custom UIs:
491
+
492
+ ```tsx
493
+ import {
494
+ AuthContainer,
495
+ AuthHeader,
496
+ AuthFormField,
497
+ AuthPasswordField,
498
+ AuthSubmitButton,
499
+ AuthErrorBanner,
500
+ AuthDivider,
501
+ AuthOAuthProviders,
502
+ AuthLink,
503
+ } from '@insforge/react';
504
+
505
+ function CompletelyCustomAuth() {
506
+ return (
507
+ <AuthContainer
508
+ appearance={{
509
+ containerClassName: "max-w-md",
510
+ cardClassName: "bg-white shadow-2xl"
511
+ }}
512
+ >
513
+ <AuthHeader
514
+ title="Welcome to MyApp"
515
+ subtitle="Sign in to continue"
516
+ appearance={{
517
+ titleClassName: "text-3xl text-blue-900"
518
+ }}
519
+ />
520
+
521
+ <AuthErrorBanner error={error} />
522
+
523
+ <form onSubmit={handleSubmit}>
524
+ <AuthFormField
525
+ id="email"
526
+ type="email"
527
+ label="Email"
528
+ value={email}
529
+ onChange={(e) => setEmail(e.target.value)}
530
+ appearance={{
531
+ inputClassName: "border-blue-500"
532
+ }}
533
+ />
534
+
535
+ <AuthPasswordField
536
+ id="password"
537
+ label="Password"
538
+ value={password}
539
+ onChange={(e) => setPassword(e.target.value)}
540
+ emailAuthConfig={config}
541
+ showStrengthIndicator
542
+ />
543
+
544
+ <AuthSubmitButton isLoading={loading}>
545
+ Sign In
546
+ </AuthSubmitButton>
547
+ </form>
548
+
549
+ <AuthDivider text="or" />
550
+
551
+ <AuthOAuthProviders
552
+ providers={['google', 'github', 'discord']}
553
+ onClick={handleOAuth}
554
+ loading={oauthLoading}
555
+ />
556
+
557
+ <AuthLink
558
+ text="Don't have an account?"
559
+ linkText="Sign up"
560
+ href="/sign-up"
561
+ />
562
+ </AuthContainer>
563
+ );
564
+ }
565
+ ```
566
+
567
+ **Available Atomic Components:**
568
+ - `AuthContainer` - Main wrapper with branding
569
+ - `AuthHeader` - Title and subtitle
570
+ - `AuthErrorBanner` - Error messages
571
+ - `AuthFormField` - Standard input
572
+ - `AuthPasswordField` - Password with toggle
573
+ - `AuthPasswordStrengthIndicator` - Password checklist
574
+ - `AuthSubmitButton` - Loading button
575
+ - `AuthLink` - Navigation link
576
+ - `AuthDivider` - Visual separator
577
+ - `AuthOAuthButton` - Single OAuth button
578
+ - `AuthOAuthProviders` - OAuth grid
579
+ - `AuthVerificationCodeInput` - 6-digit OTP
580
+ - `AuthBranding` - Insforge branding
581
+
582
+ ---
583
+
584
+ ## Customization
585
+
586
+ ### Appearance Props
587
+
588
+ All components support Tailwind className overrides:
589
+
590
+ ```tsx
591
+ <SignIn
592
+ appearance={{
593
+ containerClassName: "shadow-2xl max-w-lg",
594
+ cardClassName: "bg-gradient-to-br from-blue-50 to-white",
595
+ formClassName: "space-y-6",
596
+ buttonClassName: "bg-blue-600 hover:bg-blue-700 h-12"
597
+ }}
598
+ />
599
+
600
+ <UserButton
601
+ appearance={{
602
+ buttonClassName: "hover:bg-gray-100 rounded-full",
603
+ nameClassName: "text-gray-900 font-semibold",
604
+ emailClassName: "text-gray-500",
605
+ dropdownClassName: "shadow-xl"
606
+ }}
607
+ />
608
+ ```
609
+
610
+ ### Text Customization
611
+
612
+ All text is customizable:
613
+
614
+ ```tsx
615
+ <SignIn
616
+ title="Welcome Back!"
617
+ subtitle="We're happy to see you again"
618
+ emailLabel="Your Email Address"
619
+ emailPlaceholder="you@company.com"
620
+ passwordLabel="Your Password"
621
+ submitButtonText="Login Now"
622
+ loadingButtonText="Signing you in..."
623
+ signUpText="New to our platform?"
624
+ signUpLinkText="Create an account"
625
+ dividerText="or continue with"
626
+ />
627
+ ```
628
+
629
+ ---
630
+
631
+ ## Framework Integration
632
+
633
+ ### Next.js (App Router)
634
+
635
+ ```tsx
636
+ // app/layout.tsx
637
+ 'use client';
638
+ import { InsforgeProvider } from '@insforge/react';
639
+ import '@insforge/react/styles.css';
640
+
641
+ export default function RootLayout({ children }) {
642
+ return (
643
+ <html>
644
+ <body>
645
+ <InsforgeProvider
646
+ baseUrl={process.env.NEXT_PUBLIC_INSFORGE_BASE_URL}
647
+ syncTokenToCookie={async (token) => {
648
+ await fetch('/api/auth', {
649
+ method: 'POST',
650
+ body: JSON.stringify({ token })
651
+ });
652
+ return true;
653
+ }}
654
+ clearCookie={async () => {
655
+ await fetch('/api/auth', { method: 'DELETE' });
656
+ }}
657
+ >
658
+ {children}
659
+ </InsforgeProvider>
660
+ </body>
661
+ </html>
662
+ );
663
+ }
664
+ ```
665
+
666
+ ### Vite / React
667
+
668
+ ```tsx
669
+ // src/main.tsx
670
+ import { StrictMode } from 'react';
671
+ import { createRoot } from 'react-dom/client';
672
+ import { BrowserRouter } from 'react-router-dom';
673
+ import { InsforgeProvider } from '@insforge/react';
674
+ import '@insforge/react/styles.css';
675
+ import App from './App';
676
+
677
+ createRoot(document.getElementById('root')!).render(
678
+ <StrictMode>
679
+ <BrowserRouter>
680
+ <InsforgeProvider baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}>
681
+ <App />
682
+ </InsforgeProvider>
683
+ </BrowserRouter>
684
+ </StrictMode>
685
+ );
686
+ ```
687
+
688
+ ### Remix
689
+
690
+ ```tsx
691
+ // app/root.tsx
692
+ import { InsforgeProvider } from '@insforge/react';
693
+ import '@insforge/react/styles.css';
694
+
695
+ export default function App() {
696
+ return (
697
+ <html>
698
+ <body>
699
+ <InsforgeProvider baseUrl={process.env.INSFORGE_BASE_URL}>
700
+ <Outlet />
701
+ </InsforgeProvider>
702
+ </body>
703
+ </html>
704
+ );
705
+ }
706
+ ```
707
+
708
+ ---
709
+
710
+ ## TypeScript
711
+
712
+ Full TypeScript support with exported types:
713
+
714
+ ```tsx
715
+ import type {
716
+ InsforgeUser,
717
+ SignInProps,
718
+ SignUpProps,
719
+ UserButtonProps,
720
+ ProtectProps,
721
+ ConditionalProps,
722
+ InsforgeCallbackProps,
723
+ SignInFormProps,
724
+ SignUpFormProps,
725
+ AuthFormFieldProps,
726
+ OAuthProvider,
727
+ EmailAuthConfig,
728
+ InsforgeProviderProps,
729
+ } from '@insforge/react';
730
+ ```
731
+
732
+ ---
733
+
734
+ ## API Reference
735
+
736
+ ### InsforgeProvider Props
737
+
738
+ ```tsx
739
+ interface InsforgeProviderProps {
740
+ baseUrl: string; // Insforge backend URL
741
+ onAuthChange?: (user: InsforgeUser | null) => void; // Auth state callback
742
+ syncTokenToCookie?: (token: string) => Promise<boolean>; // Custom cookie sync
743
+ clearCookie?: () => Promise<void>; // Custom cookie clear
744
+ }
745
+ ```
746
+
747
+ ### SignIn / SignUp Props
748
+
749
+ ```tsx
750
+ interface SignInProps {
751
+ afterSignInUrl?: string; // Redirect after sign in
752
+ signUpUrl?: string; // Link to sign up page
753
+ forgotPasswordUrl?: string; // Link to forgot password
754
+ onSuccess?: (user, token) => void; // Success callback
755
+ onError?: (error: Error) => void; // Error callback
756
+ onRedirect?: (url: string) => void; // Custom redirect handler
757
+ title?: string; // Custom title
758
+ subtitle?: string; // Custom subtitle
759
+ appearance?: { // Custom styling
760
+ containerClassName?: string;
761
+ cardClassName?: string;
762
+ formClassName?: string;
763
+ buttonClassName?: string;
764
+ };
765
+ // ... more text customization props
766
+ }
767
+ ```
768
+
769
+ ### InsforgeCallback Props
770
+
771
+ ```tsx
772
+ interface InsforgeCallbackProps {
773
+ redirectTo?: string; // Custom redirect destination
774
+ onSuccess?: () => void; // Success callback
775
+ onError?: (error: string) => void; // Error callback
776
+ loadingComponent?: ReactNode; // Custom loading UI
777
+ onRedirect?: (url: string) => void; // Custom redirect handler
778
+ }
779
+ ```
780
+
781
+ ---
782
+
783
+ ## Validation Utilities
784
+
785
+ ```tsx
786
+ import { emailSchema, cn } from '@insforge/react';
787
+
788
+ // Validate email with Zod
789
+ const result = emailSchema.safeParse('user@example.com');
790
+
791
+ // Merge Tailwind classes
792
+ const className = cn('px-4 py-2', 'bg-blue-500', conditionalClass);
793
+ ```
794
+
795
+ ---
796
+
797
+ ## OAuth Providers
798
+
799
+ Built-in support for 10+ OAuth providers:
800
+ - Google
801
+ - GitHub
802
+ - Discord
803
+ - Apple
804
+ - Microsoft
805
+ - Facebook
806
+ - LinkedIn
807
+ - Instagram
808
+ - TikTok
809
+ - Spotify
810
+ - X (Twitter)
811
+
812
+ Providers are auto-detected from your backend configuration.
813
+
814
+ ---
815
+
816
+ ## Why @insforge/react?
817
+
818
+ **vs. Building Custom Auth:**
819
+ - ⚡️ 5 minutes vs 2+ days of development
820
+ - 🔒 Production-ready security built-in
821
+ - 🎨 Customizable when needed, works out of the box
822
+ - 🚀 No framework lock-in
823
+
824
+ **vs. Other Auth Libraries:**
825
+ - 📦 Complete package (not just UI)
826
+ - 🎯 Framework agnostic (works everywhere)
827
+ - 🤖 SDK-first approach (consistent API)
828
+ - 💰 Self-hosted (no vendor lock-in)
829
+
830
+ ---
831
+
832
+ ## Examples
833
+
834
+ Check out example integrations:
835
+ - [Next.js App Router](./examples/nextjs)
836
+ - [Vite + React Router](./examples/vite)
837
+ - [Remix](./examples/remix)
838
+
839
+ ---
840
+
841
+ ## Support
842
+
843
+ - **Documentation**: https://docs.insforge.dev
844
+ - **GitHub Issues**: https://github.com/InsForge/InsForge/issues
845
+ - **Discord Community**: https://discord.com/invite/DvBtaEc9Jz
846
+
847
+ ## License
848
+
849
+ MIT © Insforge