@insforge/react 0.2.10 → 0.3.1

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 CHANGED
@@ -110,19 +110,19 @@ export default function Home() {
110
110
 
111
111
  ### Built-in Auth (Recommended)
112
112
 
113
- Uses your deployed Insforge auth pages:
113
+ Uses your deployed Insforge auth pages (includes all flows):
114
114
 
115
115
  ```tsx
116
116
  ...getInsforgeRoutes({
117
117
  baseUrl: 'https://your-project.insforge.app',
118
118
  builtInAuth: true, // Default
119
119
  paths: {
120
- signIn: '/sign-in', // Custom path (optional)
121
- signUp: '/sign-up',
122
- verifyEmail: '/verify-email',
123
- forgotPassword: '/forgot-password',
124
- resetPassword: '/reset-password',
125
- callback: '/auth/callback'
120
+ signIn: '/sign-in', // Sign in page
121
+ signUp: '/sign-up', // Sign up page
122
+ verifyEmail: '/verify-email', // Email verification page
123
+ forgotPassword: '/forgot-password', // Request password reset
124
+ resetPassword: '/reset-password', // Reset password with token
125
+ callback: '/auth/callback' // OAuth callback handler
126
126
  }
127
127
  })
128
128
  ```
@@ -132,7 +132,13 @@ Uses your deployed Insforge auth pages:
132
132
  Use package components with your own styling:
133
133
 
134
134
  ```tsx
135
- import { SignIn, SignUp } from '@insforge/react';
135
+ import {
136
+ SignIn,
137
+ SignUp,
138
+ ForgotPassword,
139
+ ResetPassword,
140
+ VerifyEmail
141
+ } from '@insforge/react';
136
142
 
137
143
  const router = createBrowserRouter([
138
144
  { path: '/', element: <Home /> },
@@ -143,9 +149,12 @@ const router = createBrowserRouter([
143
149
  builtInAuth: false // Don't redirect to deployed UI
144
150
  }),
145
151
 
146
- // Use package components
152
+ // Use package components for complete auth flows
147
153
  { path: '/sign-in', element: <SignIn afterSignInUrl="/dashboard" /> },
148
- { path: '/sign-up', element: <SignUp afterSignUpUrl="/dashboard" /> }
154
+ { path: '/sign-up', element: <SignUp afterSignUpUrl="/dashboard" /> },
155
+ { path: '/forgot-password', element: <ForgotPassword backToSignInUrl="/sign-in" /> },
156
+ { path: '/reset-password', element: <ResetPassword token={searchParams.get('token')} /> },
157
+ { path: '/verify-email', element: <VerifyEmail token={searchParams.get('token')} /> }
149
158
  ]);
150
159
  ```
151
160
 
@@ -177,7 +186,10 @@ function CustomSignIn() {
177
186
 
178
187
  **Pre-built with Business Logic:**
179
188
  - `<SignIn />` - Complete sign-in with email/password & OAuth
180
- - `<SignUp />` - Registration with password validation
189
+ - `<SignUp />` - Registration with password validation & email verification
190
+ - `<ForgotPassword />` - Request password reset with email validation
191
+ - `<ResetPassword />` - Reset password with token validation
192
+ - `<VerifyEmail />` - Verify email with automatic token handling
181
193
  - `<UserButton />` - User dropdown with sign-out
182
194
  - `<Protect />` - Route protection wrapper
183
195
  - `<SignedIn>` / `<SignedOut>` - Conditional rendering
@@ -186,12 +198,12 @@ function CustomSignIn() {
186
198
  **Form Components (Pure UI):**
187
199
  - `<SignInForm />` - Sign-in UI without logic
188
200
  - `<SignUpForm />` - Sign-up UI without logic
189
- - `<ForgotPasswordForm />` - Password reset request
190
- - `<ResetPasswordForm />` - Password reset with token
191
- - `<VerifyEmailStatus />` - Email verification status
201
+ - `<ForgotPasswordForm />` - Password reset request UI
202
+ - `<ResetPasswordForm />` - Password reset with token UI
203
+ - `<VerifyEmailStatus />` - Email verification status UI
192
204
 
193
- **Atomic Components (13 total):**
194
- - `<AuthContainer />`, `<AuthHeader />`, `<AuthFormField />`, `<AuthPasswordField />`, etc.
205
+ **Atomic Components (14 total):**
206
+ - `<AuthContainer />`, `<AuthHeader />`, `<AuthFormField />`, `<AuthPasswordField />`, `<AuthEmailVerificationStep />`, etc.
195
207
 
196
208
  ### Hooks
197
209
 
@@ -217,6 +229,14 @@ All components support `appearance` props:
217
229
  button: "bg-blue-600 hover:bg-blue-700"
218
230
  }}
219
231
  />
232
+
233
+ // Works with all auth components
234
+ <ForgotPassword
235
+ appearance={{
236
+ card: "bg-gradient-to-br from-indigo-50 to-white",
237
+ button: "bg-indigo-600 hover:bg-indigo-700"
238
+ }}
239
+ />
220
240
  ```
221
241
 
222
242
  ### Deep Customization (Hierarchical Appearance)
@@ -255,7 +275,7 @@ Style nested components through hierarchical structure:
255
275
 
256
276
  #### Complete Appearance Structure
257
277
 
258
- **SignIn / SignUp Components:**
278
+ **All auth components** (`SignIn`, `SignUp`, `ForgotPassword`, `ResetPassword`) support hierarchical appearance:
259
279
 
260
280
  ```typescript
261
281
  appearance?: {
@@ -269,17 +289,17 @@ appearance?: {
269
289
  errorBanner?: string; // Error message banner
270
290
  form?: {
271
291
  container?: string; // Form wrapper
272
- emailField?: {
273
- container?: string; // Email field wrapper
274
- label?: string; // Email label
275
- input?: string; // Email input
292
+ emailField?: { // Available in SignIn, SignUp, ForgotPassword
293
+ container?: string;
294
+ label?: string;
295
+ input?: string;
276
296
  };
277
- passwordField?: {
278
- container?: string; // Password field wrapper
279
- label?: string; // Password label
280
- input?: string; // Password input
281
- forgotPasswordLink?: string; // Forgot password link (SignIn only)
282
- strengthIndicator?: { // Password strength (SignUp only)
297
+ passwordField?: { // Available in SignIn, SignUp, ResetPassword
298
+ container?: string;
299
+ label?: string;
300
+ input?: string;
301
+ forgotPasswordLink?: string; // SignIn only
302
+ strengthIndicator?: { // SignUp and ResetPassword
283
303
  container?: string;
284
304
  requirement?: string;
285
305
  };
@@ -291,17 +311,17 @@ appearance?: {
291
311
  text?: string; // Link description text
292
312
  link?: string; // Actual link element
293
313
  };
294
- divider?: string; // "or" divider
295
- oauth?: {
296
- container?: string; // OAuth buttons wrapper
297
- button?: string; // Individual OAuth button
314
+ divider?: string; // "or" divider (SignIn, SignUp)
315
+ oauth?: { // OAuth section (SignIn, SignUp)
316
+ container?: string;
317
+ button?: string;
298
318
  };
299
319
  }
300
320
  ```
301
321
 
302
322
  ### Text Customization
303
323
 
304
- All text is customizable:
324
+ All components support full text customization:
305
325
 
306
326
  ```tsx
307
327
  <SignIn
@@ -316,6 +336,16 @@ All text is customizable:
316
336
  signUpLinkText="Create an account"
317
337
  dividerText="or continue with"
318
338
  />
339
+
340
+ <ForgotPassword
341
+ title="Reset Your Password"
342
+ subtitle="Enter your email to receive a reset code"
343
+ emailLabel="Email Address"
344
+ submitButtonText="Send Reset Code"
345
+ backToSignInText="Remember your password?"
346
+ successTitle="Check Your Email"
347
+ successMessage="We've sent a reset code to your inbox"
348
+ />
319
349
  ```
320
350
 
321
351
  ---
@@ -483,14 +513,22 @@ import type {
483
513
  InsforgeUser,
484
514
  SignInProps,
485
515
  SignUpProps,
516
+ ForgotPasswordProps,
517
+ ResetPasswordProps,
518
+ VerifyEmailProps,
486
519
  SignInAppearance,
487
520
  SignUpAppearance,
521
+ ForgotPasswordAppearance,
522
+ ResetPasswordAppearance,
488
523
  UserButtonProps,
489
524
  ProtectProps,
490
525
  ConditionalProps,
491
526
  InsforgeCallbackProps,
492
527
  SignInFormProps,
493
528
  SignUpFormProps,
529
+ ForgotPasswordFormProps,
530
+ ResetPasswordFormProps,
531
+ VerifyEmailStatusProps,
494
532
  AuthFormFieldProps,
495
533
  OAuthProvider,
496
534
  EmailAuthConfig,
@@ -551,6 +589,7 @@ Low-level building blocks for complete customization:
551
589
  - `<AuthOAuthButton />` - Single OAuth provider button
552
590
  - `<AuthOAuthProviders />` - Smart OAuth grid
553
591
  - `<AuthVerificationCodeInput />` - 6-digit OTP input
592
+ - `<AuthEmailVerificationStep />` - Email verification step with countdown and resend
554
593
 
555
594
  ---
556
595
 
package/dist/atoms.d.mts CHANGED
@@ -361,6 +361,27 @@ declare function AuthOAuthProviders({ providers, onClick, disabled, loading, app
361
361
  * - `appearance.containerClassName`: Container element
362
362
  * - `appearance.inputClassName`: Input elements
363
363
  */
364
- declare function AuthVerificationCodeInput({ length, value, email, onChange, disabled, appearance, }: AuthVerificationCodeInputProps): react_jsx_runtime.JSX.Element;
364
+ declare function AuthVerificationCodeInput({ length, value, email, onChange, disabled, onComplete, appearance, }: AuthVerificationCodeInputProps): react_jsx_runtime.JSX.Element;
365
365
 
366
- export { AuthBranding, AuthContainer, AuthDivider, AuthErrorBanner, AuthFormField, AuthHeader, AuthLink, AuthOAuthButton, AuthOAuthProviders, AuthPasswordField, AuthPasswordStrengthIndicator, AuthSubmitButton, AuthVerificationCodeInput, validatePasswordStrength };
366
+ type VerificationMethod = 'code' | 'link';
367
+ interface AuthEmailVerificationStepProps {
368
+ email: string;
369
+ description?: string;
370
+ method?: VerificationMethod;
371
+ onVerifyCode?: (code: string) => Promise<void>;
372
+ }
373
+ /**
374
+ * Email verification step component (pure UI, no container)
375
+ *
376
+ * Designed to be embedded within a form container.
377
+ * Handles the email verification flow:
378
+ * - Automatically sends verification email on mount
379
+ * - Shows countdown timer for resend functionality
380
+ * - Manages rate limiting for resend attempts
381
+ * - Supports both code and link verification methods
382
+ *
383
+ * @param method - 'code' for OTP input, 'link' for magic link (default: 'code')
384
+ */
385
+ declare function AuthEmailVerificationStep({ email, description, method, onVerifyCode, }: AuthEmailVerificationStepProps): react_jsx_runtime.JSX.Element;
386
+
387
+ export { AuthBranding, AuthContainer, AuthDivider, AuthEmailVerificationStep, AuthErrorBanner, AuthFormField, AuthHeader, AuthLink, AuthOAuthButton, AuthOAuthProviders, AuthPasswordField, AuthPasswordStrengthIndicator, AuthSubmitButton, AuthVerificationCodeInput, validatePasswordStrength };
package/dist/atoms.d.ts CHANGED
@@ -361,6 +361,27 @@ declare function AuthOAuthProviders({ providers, onClick, disabled, loading, app
361
361
  * - `appearance.containerClassName`: Container element
362
362
  * - `appearance.inputClassName`: Input elements
363
363
  */
364
- declare function AuthVerificationCodeInput({ length, value, email, onChange, disabled, appearance, }: AuthVerificationCodeInputProps): react_jsx_runtime.JSX.Element;
364
+ declare function AuthVerificationCodeInput({ length, value, email, onChange, disabled, onComplete, appearance, }: AuthVerificationCodeInputProps): react_jsx_runtime.JSX.Element;
365
365
 
366
- export { AuthBranding, AuthContainer, AuthDivider, AuthErrorBanner, AuthFormField, AuthHeader, AuthLink, AuthOAuthButton, AuthOAuthProviders, AuthPasswordField, AuthPasswordStrengthIndicator, AuthSubmitButton, AuthVerificationCodeInput, validatePasswordStrength };
366
+ type VerificationMethod = 'code' | 'link';
367
+ interface AuthEmailVerificationStepProps {
368
+ email: string;
369
+ description?: string;
370
+ method?: VerificationMethod;
371
+ onVerifyCode?: (code: string) => Promise<void>;
372
+ }
373
+ /**
374
+ * Email verification step component (pure UI, no container)
375
+ *
376
+ * Designed to be embedded within a form container.
377
+ * Handles the email verification flow:
378
+ * - Automatically sends verification email on mount
379
+ * - Shows countdown timer for resend functionality
380
+ * - Manages rate limiting for resend attempts
381
+ * - Supports both code and link verification methods
382
+ *
383
+ * @param method - 'code' for OTP input, 'link' for magic link (default: 'code')
384
+ */
385
+ declare function AuthEmailVerificationStep({ email, description, method, onVerifyCode, }: AuthEmailVerificationStepProps): react_jsx_runtime.JSX.Element;
386
+
387
+ export { AuthBranding, AuthContainer, AuthDivider, AuthEmailVerificationStep, AuthErrorBanner, AuthFormField, AuthHeader, AuthLink, AuthOAuthButton, AuthOAuthProviders, AuthPasswordField, AuthPasswordStrengthIndicator, AuthSubmitButton, AuthVerificationCodeInput, validatePasswordStrength };
package/dist/atoms.js CHANGED
@@ -5,6 +5,7 @@ var clsx = require('clsx');
5
5
  var tailwindMerge = require('tailwind-merge');
6
6
  var lucideReact = require('lucide-react');
7
7
  var react = require('react');
8
+ var sdk = require('@insforge/sdk');
8
9
 
9
10
  function AuthBranding() {
10
11
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-[#FAFAFA] px-2 py-4 flex flex-row justify-center items-center gap-1", children: [
@@ -655,6 +656,7 @@ function AuthVerificationCodeInput({
655
656
  email,
656
657
  onChange,
657
658
  disabled = false,
659
+ onComplete,
658
660
  appearance = {}
659
661
  }) {
660
662
  const inputRefs = react.useRef([]);
@@ -668,6 +670,9 @@ function AuthVerificationCodeInput({
668
670
  if (digit && index < length - 1) {
669
671
  inputRefs.current[index + 1]?.focus();
670
672
  }
673
+ if (digit && index === length - 1 && updatedValue.length === length && onComplete) {
674
+ onComplete(updatedValue);
675
+ }
671
676
  };
672
677
  const handleKeyDown = (index, e) => {
673
678
  if (e.key === "Backspace") {
@@ -688,6 +693,9 @@ function AuthVerificationCodeInput({
688
693
  if (/^\d+$/.test(pastedData) && pastedData.length === length) {
689
694
  onChange(pastedData);
690
695
  inputRefs.current[length - 1]?.focus();
696
+ if (onComplete) {
697
+ onComplete(pastedData);
698
+ }
691
699
  }
692
700
  };
693
701
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(
@@ -728,10 +736,140 @@ function AuthVerificationCodeInput({
728
736
  )) })
729
737
  ] });
730
738
  }
739
+ var InsforgeContext = react.createContext(
740
+ void 0
741
+ );
742
+ function useInsforge() {
743
+ const context = react.useContext(InsforgeContext);
744
+ if (!context) {
745
+ throw new Error("useInsforge must be used within InsforgeProvider");
746
+ }
747
+ return context;
748
+ }
749
+ function AuthEmailVerificationStep({
750
+ email,
751
+ description,
752
+ method = "code",
753
+ onVerifyCode
754
+ }) {
755
+ const { baseUrl } = useInsforge();
756
+ const [insforge] = react.useState(() => sdk.createClient({ baseUrl }));
757
+ const [resendDisabled, setResendDisabled] = react.useState(true);
758
+ const [resendCountdown, setResendCountdown] = react.useState(60);
759
+ const [isSending, setIsSending] = react.useState(false);
760
+ const [verificationCode, setVerificationCode] = react.useState("");
761
+ const [isVerifying, setIsVerifying] = react.useState(false);
762
+ const [verificationError, setVerificationError] = react.useState("");
763
+ const defaultDescription = method === "code" ? "We've sent a 6-digit verification code to {email}. Please enter it below to verify your account. The code will expire in 10 minutes." : "We've sent a verification link to {email}. Please check your email and click the link to verify your account. The link will expire in 10 minutes.";
764
+ react.useEffect(() => {
765
+ const sendInitialEmail = async () => {
766
+ try {
767
+ if (method === "code") {
768
+ await insforge.auth.sendVerificationCode({ email });
769
+ } else {
770
+ await insforge.auth.sendVerificationLink({ email });
771
+ }
772
+ } catch {
773
+ }
774
+ };
775
+ void sendInitialEmail();
776
+ }, [email, method, insforge.auth]);
777
+ react.useEffect(() => {
778
+ if (resendCountdown > 0) {
779
+ const timer = setInterval(() => {
780
+ setResendCountdown((prev) => {
781
+ if (prev <= 1) {
782
+ setResendDisabled(false);
783
+ return 0;
784
+ }
785
+ return prev - 1;
786
+ });
787
+ }, 1e3);
788
+ return () => clearInterval(timer);
789
+ }
790
+ }, [resendCountdown]);
791
+ const handleResend = async () => {
792
+ setResendDisabled(true);
793
+ setResendCountdown(60);
794
+ setIsSending(true);
795
+ setVerificationError("");
796
+ try {
797
+ if (method === "code") {
798
+ await insforge.auth.sendVerificationCode({ email });
799
+ } else {
800
+ await insforge.auth.sendVerificationLink({ email });
801
+ }
802
+ } catch {
803
+ setResendDisabled(false);
804
+ setResendCountdown(0);
805
+ } finally {
806
+ setIsSending(false);
807
+ }
808
+ };
809
+ const handleVerifyCode = async (code) => {
810
+ if (!onVerifyCode) {
811
+ return;
812
+ }
813
+ setIsVerifying(true);
814
+ setVerificationError("");
815
+ try {
816
+ await onVerifyCode(code);
817
+ } catch (error) {
818
+ setVerificationError(
819
+ error instanceof Error ? error.message : "Invalid verification code. Please try again."
820
+ );
821
+ setVerificationCode("");
822
+ } finally {
823
+ setIsVerifying(false);
824
+ }
825
+ };
826
+ const displayDescription = description || defaultDescription;
827
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-6 items-stretch", children: [
828
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed", children: displayDescription.split("{email}").map((part, index, array) => /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
829
+ part,
830
+ index < array.length - 1 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-black dark:text-white", children: email })
831
+ ] }, index)) }),
832
+ verificationError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pl-3 py-2 pr-2 bg-red-50 border-2 border-red-600 rounded", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
833
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-6 h-6 text-red-500 shrink-0", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" }) }),
834
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-red-600 flex-1", children: verificationError })
835
+ ] }) }),
836
+ method === "code" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full bg-neutral-100 dark:bg-neutral-800 rounded-lg px-4 pt-4 pb-6 flex flex-col gap-4", children: [
837
+ /* @__PURE__ */ jsxRuntime.jsx(
838
+ AuthVerificationCodeInput,
839
+ {
840
+ value: verificationCode,
841
+ onChange: setVerificationCode,
842
+ email,
843
+ disabled: isVerifying,
844
+ onComplete: (code) => {
845
+ void handleVerifyCode(code);
846
+ }
847
+ }
848
+ ),
849
+ isVerifying && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 text-center", children: "Verifying..." })
850
+ ] }),
851
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full text-sm text-center text-neutral-600 dark:text-neutral-400", children: [
852
+ "Didn't receive the email?",
853
+ " ",
854
+ /* @__PURE__ */ jsxRuntime.jsx(
855
+ "button",
856
+ {
857
+ onClick: () => {
858
+ void handleResend();
859
+ },
860
+ disabled: resendDisabled || isSending,
861
+ className: "text-black dark:text-white font-medium transition-colors disabled:cursor-not-allowed cursor-pointer hover:underline disabled:no-underline disabled:opacity-50",
862
+ children: isSending ? "Sending..." : resendDisabled ? `Retry in (${resendCountdown}s)` : "Click to resend"
863
+ }
864
+ )
865
+ ] })
866
+ ] });
867
+ }
731
868
 
732
869
  exports.AuthBranding = AuthBranding;
733
870
  exports.AuthContainer = AuthContainer;
734
871
  exports.AuthDivider = AuthDivider;
872
+ exports.AuthEmailVerificationStep = AuthEmailVerificationStep;
735
873
  exports.AuthErrorBanner = AuthErrorBanner;
736
874
  exports.AuthFormField = AuthFormField;
737
875
  exports.AuthHeader = AuthHeader;