@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.
@@ -1,9 +1,10 @@
1
- import { createContext, useState, useRef, useEffect, useContext } from 'react';
1
+ import { createContext, useState, useRef, useEffect, useCallback, useContext } from 'react';
2
2
  import { createClient } from '@insforge/sdk';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
  import { AlertTriangle, Check, EyeOff, Eye, Loader2, CircleCheck, LogOut } from 'lucide-react';
7
+ import { z } from 'zod';
7
8
 
8
9
  var InsforgeContext = createContext(
9
10
  void 0
@@ -683,6 +684,7 @@ function AuthVerificationCodeInput({
683
684
  email,
684
685
  onChange,
685
686
  disabled = false,
687
+ onComplete,
686
688
  appearance = {}
687
689
  }) {
688
690
  const inputRefs = useRef([]);
@@ -696,6 +698,9 @@ function AuthVerificationCodeInput({
696
698
  if (digit && index < length - 1) {
697
699
  inputRefs.current[index + 1]?.focus();
698
700
  }
701
+ if (digit && index === length - 1 && updatedValue.length === length && onComplete) {
702
+ onComplete(updatedValue);
703
+ }
699
704
  };
700
705
  const handleKeyDown = (index, e) => {
701
706
  if (e.key === "Backspace") {
@@ -716,6 +721,9 @@ function AuthVerificationCodeInput({
716
721
  if (/^\d+$/.test(pastedData) && pastedData.length === length) {
717
722
  onChange(pastedData);
718
723
  inputRefs.current[length - 1]?.focus();
724
+ if (onComplete) {
725
+ onComplete(pastedData);
726
+ }
719
727
  }
720
728
  };
721
729
  return /* @__PURE__ */ jsxs("div", { className: cn(
@@ -756,6 +764,125 @@ function AuthVerificationCodeInput({
756
764
  )) })
757
765
  ] });
758
766
  }
767
+ function AuthEmailVerificationStep({
768
+ email,
769
+ description,
770
+ method = "code",
771
+ onVerifyCode
772
+ }) {
773
+ const { baseUrl } = useInsforge();
774
+ const [insforge] = useState(() => createClient({ baseUrl }));
775
+ const [resendDisabled, setResendDisabled] = useState(true);
776
+ const [resendCountdown, setResendCountdown] = useState(60);
777
+ const [isSending, setIsSending] = useState(false);
778
+ const [verificationCode, setVerificationCode] = useState("");
779
+ const [isVerifying, setIsVerifying] = useState(false);
780
+ const [verificationError, setVerificationError] = useState("");
781
+ 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.";
782
+ useEffect(() => {
783
+ const sendInitialEmail = async () => {
784
+ try {
785
+ if (method === "code") {
786
+ await insforge.auth.sendVerificationCode({ email });
787
+ } else {
788
+ await insforge.auth.sendVerificationLink({ email });
789
+ }
790
+ } catch {
791
+ }
792
+ };
793
+ void sendInitialEmail();
794
+ }, [email, method, insforge.auth]);
795
+ useEffect(() => {
796
+ if (resendCountdown > 0) {
797
+ const timer = setInterval(() => {
798
+ setResendCountdown((prev) => {
799
+ if (prev <= 1) {
800
+ setResendDisabled(false);
801
+ return 0;
802
+ }
803
+ return prev - 1;
804
+ });
805
+ }, 1e3);
806
+ return () => clearInterval(timer);
807
+ }
808
+ }, [resendCountdown]);
809
+ const handleResend = async () => {
810
+ setResendDisabled(true);
811
+ setResendCountdown(60);
812
+ setIsSending(true);
813
+ setVerificationError("");
814
+ try {
815
+ if (method === "code") {
816
+ await insforge.auth.sendVerificationCode({ email });
817
+ } else {
818
+ await insforge.auth.sendVerificationLink({ email });
819
+ }
820
+ } catch {
821
+ setResendDisabled(false);
822
+ setResendCountdown(0);
823
+ } finally {
824
+ setIsSending(false);
825
+ }
826
+ };
827
+ const handleVerifyCode = async (code) => {
828
+ if (!onVerifyCode) {
829
+ return;
830
+ }
831
+ setIsVerifying(true);
832
+ setVerificationError("");
833
+ try {
834
+ await onVerifyCode(code);
835
+ } catch (error) {
836
+ setVerificationError(
837
+ error instanceof Error ? error.message : "Invalid verification code. Please try again."
838
+ );
839
+ setVerificationCode("");
840
+ } finally {
841
+ setIsVerifying(false);
842
+ }
843
+ };
844
+ const displayDescription = description || defaultDescription;
845
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6 items-stretch", children: [
846
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed", children: displayDescription.split("{email}").map((part, index, array) => /* @__PURE__ */ jsxs("span", { children: [
847
+ part,
848
+ index < array.length - 1 && /* @__PURE__ */ jsx("span", { className: "font-medium text-black dark:text-white", children: email })
849
+ ] }, index)) }),
850
+ verificationError && /* @__PURE__ */ jsx("div", { className: "pl-3 py-2 pr-2 bg-red-50 border-2 border-red-600 rounded", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
851
+ /* @__PURE__ */ 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__ */ 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" }) }),
852
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600 flex-1", children: verificationError })
853
+ ] }) }),
854
+ method === "code" && /* @__PURE__ */ 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: [
855
+ /* @__PURE__ */ jsx(
856
+ AuthVerificationCodeInput,
857
+ {
858
+ value: verificationCode,
859
+ onChange: setVerificationCode,
860
+ email,
861
+ disabled: isVerifying,
862
+ onComplete: (code) => {
863
+ void handleVerifyCode(code);
864
+ }
865
+ }
866
+ ),
867
+ isVerifying && /* @__PURE__ */ jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 text-center", children: "Verifying..." })
868
+ ] }),
869
+ /* @__PURE__ */ jsxs("div", { className: "w-full text-sm text-center text-neutral-600 dark:text-neutral-400", children: [
870
+ "Didn't receive the email?",
871
+ " ",
872
+ /* @__PURE__ */ jsx(
873
+ "button",
874
+ {
875
+ onClick: () => {
876
+ void handleResend();
877
+ },
878
+ disabled: resendDisabled || isSending,
879
+ className: "text-black dark:text-white font-medium transition-colors disabled:cursor-not-allowed cursor-pointer hover:underline disabled:no-underline disabled:opacity-50",
880
+ children: isSending ? "Sending..." : resendDisabled ? `Retry in (${resendCountdown}s)` : "Click to resend"
881
+ }
882
+ )
883
+ ] })
884
+ ] });
885
+ }
759
886
  function SignInForm({
760
887
  email,
761
888
  password,
@@ -782,7 +909,11 @@ function SignInForm({
782
909
  signUpText = "Don't have an account?",
783
910
  signUpLinkText = "Sign Up Now",
784
911
  signUpUrl = "/sign-up",
785
- dividerText = "or"
912
+ dividerText = "or",
913
+ // Email verification step props
914
+ showVerificationStep = false,
915
+ onVerifyCode,
916
+ verificationDescription
786
917
  }) {
787
918
  return /* @__PURE__ */ jsxs(
788
919
  AuthContainer,
@@ -795,8 +926,8 @@ function SignInForm({
795
926
  /* @__PURE__ */ jsx(
796
927
  AuthHeader,
797
928
  {
798
- title,
799
- subtitle,
929
+ title: showVerificationStep ? "Verify Your Email" : title,
930
+ subtitle: showVerificationStep ? "" : subtitle,
800
931
  appearance: {
801
932
  containerClassName: appearance.header?.container,
802
933
  titleClassName: appearance.header?.title,
@@ -811,7 +942,14 @@ function SignInForm({
811
942
  className: appearance.errorBanner
812
943
  }
813
944
  ),
814
- /* @__PURE__ */ jsxs(
945
+ showVerificationStep ? /* @__PURE__ */ jsx(
946
+ AuthEmailVerificationStep,
947
+ {
948
+ email,
949
+ description: verificationDescription,
950
+ onVerifyCode
951
+ }
952
+ ) : /* @__PURE__ */ jsxs(
815
953
  "form",
816
954
  {
817
955
  onSubmit,
@@ -870,39 +1008,41 @@ function SignInForm({
870
1008
  ]
871
1009
  }
872
1010
  ),
873
- /* @__PURE__ */ jsx(
874
- AuthLink,
875
- {
876
- text: signUpText,
877
- linkText: signUpLinkText,
878
- href: signUpUrl,
879
- appearance: {
880
- containerClassName: appearance.link?.container,
881
- linkClassName: appearance.link?.link
882
- }
883
- }
884
- ),
885
- availableProviders.length > 0 && onOAuthClick && /* @__PURE__ */ jsxs(Fragment, { children: [
886
- /* @__PURE__ */ jsx(
887
- AuthDivider,
888
- {
889
- text: dividerText,
890
- className: appearance.divider
891
- }
892
- ),
1011
+ !showVerificationStep && /* @__PURE__ */ jsxs(Fragment, { children: [
893
1012
  /* @__PURE__ */ jsx(
894
- AuthOAuthProviders,
1013
+ AuthLink,
895
1014
  {
896
- providers: availableProviders,
897
- onClick: onOAuthClick,
898
- disabled: loading || oauthLoading !== null,
899
- loading: oauthLoading,
1015
+ text: signUpText,
1016
+ linkText: signUpLinkText,
1017
+ href: signUpUrl,
900
1018
  appearance: {
901
- containerClassName: appearance.oauth?.container,
902
- buttonClassName: appearance.oauth?.button
1019
+ containerClassName: appearance.link?.container,
1020
+ linkClassName: appearance.link?.link
903
1021
  }
904
1022
  }
905
- )
1023
+ ),
1024
+ availableProviders.length > 0 && onOAuthClick && /* @__PURE__ */ jsxs(Fragment, { children: [
1025
+ /* @__PURE__ */ jsx(
1026
+ AuthDivider,
1027
+ {
1028
+ text: dividerText,
1029
+ className: appearance.divider
1030
+ }
1031
+ ),
1032
+ /* @__PURE__ */ jsx(
1033
+ AuthOAuthProviders,
1034
+ {
1035
+ providers: availableProviders,
1036
+ onClick: onOAuthClick,
1037
+ disabled: loading || oauthLoading !== null,
1038
+ loading: oauthLoading,
1039
+ appearance: {
1040
+ containerClassName: appearance.oauth?.container,
1041
+ buttonClassName: appearance.oauth?.button
1042
+ }
1043
+ }
1044
+ )
1045
+ ] })
906
1046
  ] })
907
1047
  ]
908
1048
  }
@@ -920,6 +1060,7 @@ function SignIn({
920
1060
  const [password, setPassword] = useState("");
921
1061
  const [error, setError] = useState("");
922
1062
  const [loading, setLoading] = useState(false);
1063
+ const [step, setStep] = useState("form");
923
1064
  const [oauthLoading, setOauthLoading] = useState(
924
1065
  null
925
1066
  );
@@ -931,6 +1072,11 @@ function SignIn({
931
1072
  try {
932
1073
  const result = await signIn(email, password);
933
1074
  if ("error" in result) {
1075
+ if (result.statusCode === 403) {
1076
+ setStep("awaiting-verification");
1077
+ setLoading(false);
1078
+ return;
1079
+ }
934
1080
  throw new Error(result.error);
935
1081
  }
936
1082
  const { user, accessToken } = result;
@@ -945,6 +1091,21 @@ function SignIn({
945
1091
  setLoading(false);
946
1092
  }
947
1093
  }
1094
+ async function handleVerifyCode(code) {
1095
+ try {
1096
+ const result = await insforge.auth.verifyEmail({ email, otp: code });
1097
+ if (result.error) {
1098
+ throw new Error(result.error.message || "Verification failed");
1099
+ }
1100
+ if (result.data?.accessToken) {
1101
+ if (onSuccess && result.data.user) {
1102
+ onSuccess(result.data.user, result.data.accessToken);
1103
+ }
1104
+ }
1105
+ } catch (err) {
1106
+ throw new Error(err.message || "Invalid verification code");
1107
+ }
1108
+ }
948
1109
  async function handleOAuth(provider) {
949
1110
  try {
950
1111
  setOauthLoading(provider);
@@ -977,6 +1138,8 @@ function SignIn({
977
1138
  availableProviders: emailConfig?.oAuthProviders || [],
978
1139
  onOAuthClick: handleOAuth,
979
1140
  emailAuthConfig: emailConfig,
1141
+ showVerificationStep: step === "awaiting-verification",
1142
+ onVerifyCode: handleVerifyCode,
980
1143
  ...uiProps
981
1144
  }
982
1145
  );
@@ -1005,7 +1168,11 @@ function SignUpForm({
1005
1168
  signInText = "Already have an account?",
1006
1169
  signInLinkText = "Login Now",
1007
1170
  signInUrl = "/sign-in",
1008
- dividerText = "or"
1171
+ dividerText = "or",
1172
+ // Email verification step props
1173
+ showVerificationStep = false,
1174
+ onVerifyCode,
1175
+ verificationDescription
1009
1176
  }) {
1010
1177
  return /* @__PURE__ */ jsxs(
1011
1178
  AuthContainer,
@@ -1018,8 +1185,8 @@ function SignUpForm({
1018
1185
  /* @__PURE__ */ jsx(
1019
1186
  AuthHeader,
1020
1187
  {
1021
- title,
1022
- subtitle,
1188
+ title: showVerificationStep ? "Verify Your Email" : title,
1189
+ subtitle: showVerificationStep ? "" : subtitle,
1023
1190
  appearance: {
1024
1191
  containerClassName: appearance.header?.container,
1025
1192
  titleClassName: appearance.header?.title,
@@ -1034,7 +1201,14 @@ function SignUpForm({
1034
1201
  className: appearance.errorBanner
1035
1202
  }
1036
1203
  ),
1037
- /* @__PURE__ */ jsxs(
1204
+ showVerificationStep ? /* @__PURE__ */ jsx(
1205
+ AuthEmailVerificationStep,
1206
+ {
1207
+ email,
1208
+ description: verificationDescription,
1209
+ onVerifyCode
1210
+ }
1211
+ ) : /* @__PURE__ */ jsxs(
1038
1212
  "form",
1039
1213
  {
1040
1214
  onSubmit,
@@ -1091,44 +1265,74 @@ function SignUpForm({
1091
1265
  ]
1092
1266
  }
1093
1267
  ),
1094
- /* @__PURE__ */ jsx(
1095
- AuthLink,
1096
- {
1097
- text: signInText,
1098
- linkText: signInLinkText,
1099
- href: signInUrl,
1100
- appearance: {
1101
- containerClassName: appearance.link?.container,
1102
- linkClassName: appearance.link?.link
1103
- }
1104
- }
1105
- ),
1106
- availableProviders.length > 0 && onOAuthClick && /* @__PURE__ */ jsxs(Fragment, { children: [
1107
- /* @__PURE__ */ jsx(
1108
- AuthDivider,
1109
- {
1110
- text: dividerText,
1111
- className: appearance.divider
1112
- }
1113
- ),
1268
+ !showVerificationStep && /* @__PURE__ */ jsxs(Fragment, { children: [
1114
1269
  /* @__PURE__ */ jsx(
1115
- AuthOAuthProviders,
1270
+ AuthLink,
1116
1271
  {
1117
- providers: availableProviders,
1118
- onClick: onOAuthClick,
1119
- disabled: loading || oauthLoading !== null,
1120
- loading: oauthLoading,
1272
+ text: signInText,
1273
+ linkText: signInLinkText,
1274
+ href: signInUrl,
1121
1275
  appearance: {
1122
- containerClassName: appearance.oauth?.container,
1123
- buttonClassName: appearance.oauth?.button
1276
+ containerClassName: appearance.link?.container,
1277
+ linkClassName: appearance.link?.link
1124
1278
  }
1125
1279
  }
1126
- )
1280
+ ),
1281
+ availableProviders.length > 0 && onOAuthClick && /* @__PURE__ */ jsxs(Fragment, { children: [
1282
+ /* @__PURE__ */ jsx(
1283
+ AuthDivider,
1284
+ {
1285
+ text: dividerText,
1286
+ className: appearance.divider
1287
+ }
1288
+ ),
1289
+ /* @__PURE__ */ jsx(
1290
+ AuthOAuthProviders,
1291
+ {
1292
+ providers: availableProviders,
1293
+ onClick: onOAuthClick,
1294
+ disabled: loading || oauthLoading !== null,
1295
+ loading: oauthLoading,
1296
+ appearance: {
1297
+ containerClassName: appearance.oauth?.container,
1298
+ buttonClassName: appearance.oauth?.button
1299
+ }
1300
+ }
1301
+ )
1302
+ ] })
1127
1303
  ] })
1128
1304
  ]
1129
1305
  }
1130
1306
  );
1131
1307
  }
1308
+ var emailSchema = z.string().min(1, "Email is required").email("Invalid email address");
1309
+ function createPasswordSchema(options) {
1310
+ const {
1311
+ minLength = 6,
1312
+ requireUppercase = false,
1313
+ requireLowercase = false,
1314
+ requireNumber = false,
1315
+ requireSpecialChar = false
1316
+ } = options || {};
1317
+ let schema = z.string().min(minLength, `Password must be at least ${minLength} characters`);
1318
+ if (requireUppercase) {
1319
+ schema = schema.regex(/[A-Z]/, "Password must contain at least one uppercase letter");
1320
+ }
1321
+ if (requireLowercase) {
1322
+ schema = schema.regex(/[a-z]/, "Password must contain at least one lowercase letter");
1323
+ }
1324
+ if (requireNumber) {
1325
+ schema = schema.regex(/\d/, "Password must contain at least one number");
1326
+ }
1327
+ if (requireSpecialChar) {
1328
+ schema = schema.regex(
1329
+ /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/,
1330
+ "Password must contain at least one special character"
1331
+ );
1332
+ }
1333
+ return schema;
1334
+ }
1335
+ createPasswordSchema();
1132
1336
  function SignUp({
1133
1337
  afterSignUpUrl,
1134
1338
  onSuccess,
@@ -1141,6 +1345,7 @@ function SignUp({
1141
1345
  const [password, setPassword] = useState("");
1142
1346
  const [error, setError] = useState("");
1143
1347
  const [loading, setLoading] = useState(false);
1348
+ const [step, setStep] = useState("form");
1144
1349
  const [oauthLoading, setOauthLoading] = useState(
1145
1350
  null
1146
1351
  );
@@ -1149,19 +1354,46 @@ function SignUp({
1149
1354
  e.preventDefault();
1150
1355
  setLoading(true);
1151
1356
  setError("");
1152
- if (emailConfig && !validatePasswordStrength(password, emailConfig)) {
1153
- setError("Password does not meet all requirements");
1357
+ if (!emailConfig) {
1358
+ setError("Configuration not loaded. Please refresh the page.");
1359
+ setLoading(false);
1360
+ return;
1361
+ }
1362
+ const emailValidation = emailSchema.safeParse(email);
1363
+ if (!emailValidation.success) {
1364
+ const firstError = emailValidation.error.issues[0];
1365
+ setError(firstError.message);
1366
+ setLoading(false);
1367
+ return;
1368
+ }
1369
+ const passwordZodSchema = createPasswordSchema({
1370
+ minLength: emailConfig.passwordMinLength,
1371
+ requireUppercase: emailConfig.requireUppercase,
1372
+ requireLowercase: emailConfig.requireLowercase,
1373
+ requireNumber: emailConfig.requireNumber,
1374
+ requireSpecialChar: emailConfig.requireSpecialChar
1375
+ });
1376
+ const passwordValidation = passwordZodSchema.safeParse(password);
1377
+ if (!passwordValidation.success) {
1378
+ const firstError = passwordValidation.error.issues[0];
1379
+ setError(firstError.message);
1154
1380
  setLoading(false);
1155
1381
  return;
1156
1382
  }
1157
1383
  try {
1158
- const result = await signUp(email, password);
1384
+ const result = await signUp(emailValidation.data, password);
1159
1385
  if ("error" in result) {
1160
1386
  throw new Error(result.error);
1161
1387
  }
1162
- const { user, accessToken } = result;
1163
- if (onSuccess) {
1164
- if (user) onSuccess(user, accessToken || "");
1388
+ if (result.requiresEmailVerification && !result.accessToken) {
1389
+ setStep("awaiting-verification");
1390
+ setLoading(false);
1391
+ return;
1392
+ }
1393
+ if (result.accessToken && result.user) {
1394
+ if (onSuccess) {
1395
+ onSuccess(result.user, result.accessToken);
1396
+ }
1165
1397
  }
1166
1398
  } catch (err) {
1167
1399
  const errorMessage = err.message || "Sign up failed";
@@ -1171,6 +1403,21 @@ function SignUp({
1171
1403
  setLoading(false);
1172
1404
  }
1173
1405
  }
1406
+ async function handleVerifyCode(code) {
1407
+ try {
1408
+ const result = await insforge.auth.verifyEmail({ email, otp: code });
1409
+ if (result.error) {
1410
+ throw new Error(result.error.message || "Verification failed");
1411
+ }
1412
+ if (result.data?.accessToken) {
1413
+ if (onSuccess && result.data.user) {
1414
+ onSuccess(result.data.user, result.data.accessToken);
1415
+ }
1416
+ }
1417
+ } catch (err) {
1418
+ throw new Error(err.message || "Invalid verification code");
1419
+ }
1420
+ }
1174
1421
  async function handleOAuth(provider) {
1175
1422
  try {
1176
1423
  setOauthLoading(provider);
@@ -1203,81 +1450,691 @@ function SignUp({
1203
1450
  availableProviders: emailConfig?.oAuthProviders || [],
1204
1451
  onOAuthClick: handleOAuth,
1205
1452
  emailAuthConfig: emailConfig,
1453
+ showVerificationStep: step === "awaiting-verification",
1454
+ onVerifyCode: handleVerifyCode,
1206
1455
  ...uiProps
1207
1456
  }
1208
1457
  );
1209
1458
  }
1210
- function UserButton({
1211
- afterSignOutUrl = "/",
1212
- mode = "detailed",
1213
- appearance = {}
1459
+ function ForgotPasswordForm({
1460
+ email,
1461
+ onEmailChange,
1462
+ onSubmit,
1463
+ error,
1464
+ loading = false,
1465
+ success = false,
1466
+ appearance = {},
1467
+ title = "Forgot Password?",
1468
+ subtitle = "Enter your email address and we'll send you a code to reset your password.",
1469
+ emailLabel = "Email",
1470
+ emailPlaceholder = "example@email.com",
1471
+ submitButtonText = "Send Reset Code",
1472
+ loadingButtonText = "Sending...",
1473
+ backToSignInText = "Remember your password?",
1474
+ backToSignInUrl = "/sign-in",
1475
+ successTitle = "Check Your Email",
1476
+ successMessage
1214
1477
  }) {
1215
- const { user, signOut } = useInsforge();
1216
- const [isOpen, setIsOpen] = useState(false);
1217
- const [imageError, setImageError] = useState(false);
1218
- const dropdownRef = useRef(null);
1219
- useEffect(() => {
1220
- setImageError(false);
1221
- const avatarUrl = user?.avatarUrl;
1222
- if (!avatarUrl) return;
1223
- const checkImageUrl = async () => {
1224
- try {
1225
- const response = await fetch(avatarUrl, {
1226
- method: "HEAD",
1227
- cache: "no-cache"
1228
- });
1229
- if (!response.ok) {
1230
- setImageError(true);
1231
- }
1232
- } catch (error) {
1233
- setImageError(true);
1234
- }
1235
- };
1236
- checkImageUrl();
1237
- }, [user?.avatarUrl]);
1238
- useEffect(() => {
1239
- function handleClickOutside(event) {
1240
- if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
1241
- setIsOpen(false);
1478
+ if (success) {
1479
+ return /* @__PURE__ */ jsx(
1480
+ AuthContainer,
1481
+ {
1482
+ appearance: {
1483
+ containerClassName: appearance.container,
1484
+ cardClassName: appearance.card
1485
+ },
1486
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
1487
+ /* @__PURE__ */ jsx("div", { className: "w-16 h-16 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center", children: /* @__PURE__ */ jsx("svg", { className: "w-8 h-8 text-green-600 dark:text-green-400", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M5 13l4 4L19 7" }) }) }),
1488
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold text-black dark:text-white text-center", children: successTitle }),
1489
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 text-center", children: successMessage || `We've sent a password reset link to ${email}. Please check your email and follow the instructions.` }),
1490
+ /* @__PURE__ */ jsx("a", { href: backToSignInUrl, className: "mt-4 text-black dark:text-white font-medium", children: "Back to Sign In" })
1491
+ ] })
1242
1492
  }
1243
- }
1244
- if (isOpen) {
1245
- document.addEventListener("mousedown", handleClickOutside);
1246
- }
1247
- return () => {
1248
- document.removeEventListener("mousedown", handleClickOutside);
1249
- };
1250
- }, [isOpen]);
1251
- async function handleSignOut() {
1252
- await signOut();
1253
- setIsOpen(false);
1254
- window.location.href = afterSignOutUrl;
1493
+ );
1255
1494
  }
1256
- if (!user) return null;
1257
- const initials = user.name ? user.name.charAt(0).toUpperCase() : user.email.split("@")[0].slice(0, 2).toUpperCase();
1258
1495
  return /* @__PURE__ */ jsxs(
1259
- "div",
1496
+ AuthContainer,
1260
1497
  {
1261
- className: cn("relative inline-block", appearance.containerClassName),
1262
- ref: dropdownRef,
1498
+ appearance: {
1499
+ containerClassName: appearance.container,
1500
+ cardClassName: appearance.card
1501
+ },
1263
1502
  children: [
1503
+ /* @__PURE__ */ jsx(
1504
+ AuthHeader,
1505
+ {
1506
+ title,
1507
+ subtitle,
1508
+ appearance: {
1509
+ containerClassName: appearance.header?.container,
1510
+ titleClassName: appearance.header?.title,
1511
+ subtitleClassName: appearance.header?.subtitle
1512
+ }
1513
+ }
1514
+ ),
1515
+ /* @__PURE__ */ jsx(
1516
+ AuthErrorBanner,
1517
+ {
1518
+ error: error || "",
1519
+ className: appearance.errorBanner
1520
+ }
1521
+ ),
1264
1522
  /* @__PURE__ */ jsxs(
1265
- "button",
1523
+ "form",
1266
1524
  {
1267
- className: cn(
1268
- "p-1 bg-transparent border-0 rounded-full cursor-pointer transition-all duration-200",
1269
- "flex items-center justify-center gap-2",
1270
- "hover:bg-black/5",
1271
- mode === "detailed" && "rounded-lg p-2",
1272
- appearance.buttonClassName
1273
- ),
1274
- onClick: () => setIsOpen(!isOpen),
1275
- "aria-expanded": isOpen,
1276
- "aria-haspopup": "true",
1525
+ onSubmit,
1526
+ noValidate: true,
1527
+ className: appearance.form?.container || "flex flex-col items-stretch justify-center gap-6",
1277
1528
  children: [
1278
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center w-10 h-10 bg-blue-500 rounded-full", children: user.avatarUrl && !imageError ? /* @__PURE__ */ jsx(
1279
- "img",
1280
- {
1529
+ /* @__PURE__ */ jsx(
1530
+ AuthFormField,
1531
+ {
1532
+ id: "email",
1533
+ type: "email",
1534
+ label: emailLabel,
1535
+ placeholder: emailPlaceholder,
1536
+ value: email,
1537
+ onChange: (e) => onEmailChange(e.target.value),
1538
+ required: true,
1539
+ autoComplete: "email",
1540
+ appearance: {
1541
+ containerClassName: appearance.form?.emailField?.container,
1542
+ labelClassName: appearance.form?.emailField?.label,
1543
+ inputClassName: appearance.form?.emailField?.input
1544
+ }
1545
+ }
1546
+ ),
1547
+ /* @__PURE__ */ jsx(
1548
+ AuthSubmitButton,
1549
+ {
1550
+ isLoading: loading,
1551
+ disabled: loading,
1552
+ className: appearance.button,
1553
+ children: loading ? loadingButtonText : submitButtonText
1554
+ }
1555
+ )
1556
+ ]
1557
+ }
1558
+ ),
1559
+ /* @__PURE__ */ jsx(
1560
+ AuthLink,
1561
+ {
1562
+ text: backToSignInText,
1563
+ linkText: "Back to Sign In",
1564
+ href: backToSignInUrl,
1565
+ appearance: {
1566
+ containerClassName: appearance.link?.container,
1567
+ linkClassName: appearance.link?.link
1568
+ }
1569
+ }
1570
+ )
1571
+ ]
1572
+ }
1573
+ );
1574
+ }
1575
+ function ResetPasswordForm({
1576
+ newPassword,
1577
+ confirmPassword,
1578
+ onNewPasswordChange,
1579
+ onConfirmPasswordChange,
1580
+ onSubmit,
1581
+ error,
1582
+ loading = false,
1583
+ emailAuthConfig,
1584
+ appearance = {},
1585
+ title = "Reset Password",
1586
+ subtitle = "Enter your new password below.",
1587
+ newPasswordLabel = "New Password",
1588
+ newPasswordPlaceholder = "\u2022\u2022\u2022\u2022\u2022\u2022",
1589
+ confirmPasswordLabel = "Confirm Password",
1590
+ confirmPasswordPlaceholder = "\u2022\u2022\u2022\u2022\u2022\u2022",
1591
+ submitButtonText = "Reset Password",
1592
+ loadingButtonText = "Resetting...",
1593
+ backToSignInText = "",
1594
+ backToSignInUrl = "/sign-in"
1595
+ }) {
1596
+ return /* @__PURE__ */ jsxs(
1597
+ AuthContainer,
1598
+ {
1599
+ appearance: {
1600
+ containerClassName: appearance.container,
1601
+ cardClassName: appearance.card
1602
+ },
1603
+ children: [
1604
+ /* @__PURE__ */ jsx(
1605
+ AuthHeader,
1606
+ {
1607
+ title,
1608
+ subtitle,
1609
+ appearance: {
1610
+ containerClassName: appearance.header?.container,
1611
+ titleClassName: appearance.header?.title,
1612
+ subtitleClassName: appearance.header?.subtitle
1613
+ }
1614
+ }
1615
+ ),
1616
+ /* @__PURE__ */ jsx(
1617
+ AuthErrorBanner,
1618
+ {
1619
+ error: error || "",
1620
+ className: appearance.errorBanner
1621
+ }
1622
+ ),
1623
+ /* @__PURE__ */ jsxs(
1624
+ "form",
1625
+ {
1626
+ onSubmit,
1627
+ noValidate: true,
1628
+ className: appearance.form?.container || "flex flex-col items-stretch justify-center gap-6",
1629
+ children: [
1630
+ /* @__PURE__ */ jsx(
1631
+ AuthPasswordField,
1632
+ {
1633
+ id: "newPassword",
1634
+ label: newPasswordLabel,
1635
+ placeholder: newPasswordPlaceholder,
1636
+ value: newPassword,
1637
+ onChange: (e) => onNewPasswordChange(e.target.value),
1638
+ required: true,
1639
+ autoComplete: "new-password",
1640
+ showStrengthIndicator: true,
1641
+ emailAuthConfig,
1642
+ appearance: {
1643
+ containerClassName: appearance.form?.newPasswordField?.container,
1644
+ labelClassName: appearance.form?.newPasswordField?.label,
1645
+ inputClassName: appearance.form?.newPasswordField?.input
1646
+ }
1647
+ }
1648
+ ),
1649
+ /* @__PURE__ */ jsx(
1650
+ AuthPasswordField,
1651
+ {
1652
+ id: "confirmPassword",
1653
+ label: confirmPasswordLabel,
1654
+ placeholder: confirmPasswordPlaceholder,
1655
+ value: confirmPassword,
1656
+ onChange: (e) => onConfirmPasswordChange(e.target.value),
1657
+ required: true,
1658
+ autoComplete: "new-password",
1659
+ emailAuthConfig,
1660
+ appearance: {
1661
+ containerClassName: appearance.form?.confirmPasswordField?.container,
1662
+ labelClassName: appearance.form?.confirmPasswordField?.label,
1663
+ inputClassName: appearance.form?.confirmPasswordField?.input
1664
+ }
1665
+ }
1666
+ ),
1667
+ /* @__PURE__ */ jsx(
1668
+ AuthSubmitButton,
1669
+ {
1670
+ isLoading: loading,
1671
+ disabled: loading,
1672
+ className: appearance.button,
1673
+ children: loading ? loadingButtonText : submitButtonText
1674
+ }
1675
+ )
1676
+ ]
1677
+ }
1678
+ ),
1679
+ /* @__PURE__ */ jsx(
1680
+ AuthLink,
1681
+ {
1682
+ text: backToSignInText,
1683
+ linkText: "Back to Sign In",
1684
+ href: backToSignInUrl,
1685
+ appearance: {
1686
+ containerClassName: appearance.link?.container,
1687
+ linkClassName: appearance.link?.link
1688
+ }
1689
+ }
1690
+ )
1691
+ ]
1692
+ }
1693
+ );
1694
+ }
1695
+ function ResetPassword({
1696
+ token,
1697
+ backToSignInUrl = "/sign-in",
1698
+ onSuccess,
1699
+ onError,
1700
+ ...uiProps
1701
+ }) {
1702
+ const { resetPassword } = useInsforge();
1703
+ const { emailConfig } = usePublicAuthConfig();
1704
+ const [newPassword, setNewPassword] = useState("");
1705
+ const [confirmPassword, setConfirmPassword] = useState("");
1706
+ const [error, setError] = useState("");
1707
+ const [loading, setLoading] = useState(false);
1708
+ async function handleSubmit(e) {
1709
+ e.preventDefault();
1710
+ setLoading(true);
1711
+ setError("");
1712
+ if (!emailConfig) {
1713
+ setError("Configuration not loaded. Please refresh the page.");
1714
+ setLoading(false);
1715
+ return;
1716
+ }
1717
+ if (newPassword !== confirmPassword) {
1718
+ setError("Passwords do not match");
1719
+ setLoading(false);
1720
+ return;
1721
+ }
1722
+ if (!token) {
1723
+ setError("Reset token is missing");
1724
+ setLoading(false);
1725
+ return;
1726
+ }
1727
+ const passwordZodSchema = createPasswordSchema({
1728
+ minLength: emailConfig.passwordMinLength,
1729
+ requireUppercase: emailConfig.requireUppercase,
1730
+ requireLowercase: emailConfig.requireLowercase,
1731
+ requireNumber: emailConfig.requireNumber,
1732
+ requireSpecialChar: emailConfig.requireSpecialChar
1733
+ });
1734
+ const passwordValidation = passwordZodSchema.safeParse(newPassword);
1735
+ if (!passwordValidation.success) {
1736
+ const firstError = passwordValidation.error.issues[0];
1737
+ setError(firstError.message);
1738
+ setLoading(false);
1739
+ return;
1740
+ }
1741
+ try {
1742
+ const result = await resetPassword(token, newPassword);
1743
+ if (result?.message) {
1744
+ if (onSuccess) {
1745
+ onSuccess(result.redirectTo);
1746
+ }
1747
+ } else {
1748
+ const errorMessage = "Failed to reset password";
1749
+ setError(errorMessage);
1750
+ if (onError) {
1751
+ onError(new Error(errorMessage));
1752
+ }
1753
+ }
1754
+ } catch (err) {
1755
+ const errorMessage = err.message || "Failed to reset password";
1756
+ setError(errorMessage);
1757
+ if (onError) {
1758
+ onError(new Error(errorMessage));
1759
+ }
1760
+ } finally {
1761
+ setLoading(false);
1762
+ }
1763
+ }
1764
+ if (!emailConfig) {
1765
+ return null;
1766
+ }
1767
+ return /* @__PURE__ */ jsx(
1768
+ ResetPasswordForm,
1769
+ {
1770
+ newPassword,
1771
+ confirmPassword,
1772
+ onNewPasswordChange: setNewPassword,
1773
+ onConfirmPasswordChange: setConfirmPassword,
1774
+ onSubmit: handleSubmit,
1775
+ error,
1776
+ loading,
1777
+ emailAuthConfig: emailConfig,
1778
+ backToSignInUrl,
1779
+ ...uiProps
1780
+ }
1781
+ );
1782
+ }
1783
+ function ForgotPassword({
1784
+ backToSignInUrl = "/sign-in",
1785
+ onSuccess,
1786
+ onError,
1787
+ ...uiProps
1788
+ }) {
1789
+ const { sendPasswordResetCode, baseUrl } = useInsforge();
1790
+ const { emailConfig } = usePublicAuthConfig();
1791
+ const [insforge] = useState(() => createClient({ baseUrl }));
1792
+ const [step, setStep] = useState("email");
1793
+ const [email, setEmail] = useState("");
1794
+ const [verificationCode, setVerificationCode] = useState("");
1795
+ const [resetToken, setResetToken] = useState("");
1796
+ const [error, setError] = useState("");
1797
+ const [loading, setLoading] = useState(false);
1798
+ const [success, setSuccess] = useState(false);
1799
+ const [resendDisabled, setResendDisabled] = useState(true);
1800
+ const [resendCountdown, setResendCountdown] = useState(60);
1801
+ const [isSendingCode, setIsSendingCode] = useState(false);
1802
+ const [isVerifyingCode, setIsVerifyingCode] = useState(false);
1803
+ useEffect(() => {
1804
+ if (resendCountdown > 0 && step === "code") {
1805
+ const timer = setInterval(() => {
1806
+ setResendCountdown((prev) => {
1807
+ if (prev <= 1) {
1808
+ setResendDisabled(false);
1809
+ return 0;
1810
+ }
1811
+ return prev - 1;
1812
+ });
1813
+ }, 1e3);
1814
+ return () => clearInterval(timer);
1815
+ }
1816
+ }, [resendCountdown, step]);
1817
+ async function handleEmailSubmit(e) {
1818
+ e.preventDefault();
1819
+ setLoading(true);
1820
+ setError("");
1821
+ const emailValidation = emailSchema.safeParse(email);
1822
+ if (!emailValidation.success) {
1823
+ const firstError = emailValidation.error.issues[0];
1824
+ setError(firstError.message);
1825
+ setLoading(false);
1826
+ return;
1827
+ }
1828
+ try {
1829
+ const result = await sendPasswordResetCode(emailValidation.data);
1830
+ if (result?.success) {
1831
+ if (emailConfig?.resetPasswordMethod === "link") {
1832
+ setSuccess(true);
1833
+ if (onSuccess) {
1834
+ onSuccess();
1835
+ }
1836
+ } else {
1837
+ setStep("code");
1838
+ setResendDisabled(true);
1839
+ setResendCountdown(60);
1840
+ }
1841
+ } else {
1842
+ const errorMessage = result?.message || "Failed to send reset code";
1843
+ setError(errorMessage);
1844
+ if (onError) {
1845
+ onError(new Error(errorMessage));
1846
+ }
1847
+ }
1848
+ } catch (err) {
1849
+ const errorMessage = err.message || "Failed to send reset code";
1850
+ setError(errorMessage);
1851
+ if (onError) {
1852
+ onError(new Error(errorMessage));
1853
+ }
1854
+ } finally {
1855
+ setLoading(false);
1856
+ }
1857
+ }
1858
+ async function handleVerifyCode(code) {
1859
+ setIsVerifyingCode(true);
1860
+ setError("");
1861
+ setVerificationCode(code);
1862
+ try {
1863
+ const result = await insforge.auth.verifyResetPasswordCode({ email, code });
1864
+ if (result.error) {
1865
+ throw new Error(result.error.message || "Failed to verify code");
1866
+ }
1867
+ if (result.data) {
1868
+ setResetToken(result.data.resetToken);
1869
+ setStep("password");
1870
+ }
1871
+ } catch (err) {
1872
+ setError(err.message || "Invalid verification code");
1873
+ setVerificationCode("");
1874
+ } finally {
1875
+ setIsVerifyingCode(false);
1876
+ }
1877
+ }
1878
+ const handleResendCode = useCallback(async () => {
1879
+ setResendDisabled(true);
1880
+ setResendCountdown(60);
1881
+ setIsSendingCode(true);
1882
+ setError("");
1883
+ try {
1884
+ await sendPasswordResetCode(email);
1885
+ } catch (err) {
1886
+ setError(err.message || "Failed to resend code");
1887
+ setResendDisabled(false);
1888
+ setResendCountdown(0);
1889
+ } finally {
1890
+ setIsSendingCode(false);
1891
+ }
1892
+ }, [email, sendPasswordResetCode]);
1893
+ function handlePasswordResetSuccess() {
1894
+ if (onSuccess) {
1895
+ onSuccess();
1896
+ }
1897
+ }
1898
+ if (!emailConfig) {
1899
+ return null;
1900
+ }
1901
+ if (step === "email") {
1902
+ return /* @__PURE__ */ jsx(
1903
+ ForgotPasswordForm,
1904
+ {
1905
+ email,
1906
+ onEmailChange: setEmail,
1907
+ onSubmit: handleEmailSubmit,
1908
+ error,
1909
+ loading,
1910
+ success,
1911
+ backToSignInUrl,
1912
+ ...uiProps
1913
+ }
1914
+ );
1915
+ }
1916
+ if (step === "code") {
1917
+ return /* @__PURE__ */ jsxs(
1918
+ AuthContainer,
1919
+ {
1920
+ appearance: {
1921
+ containerClassName: uiProps.appearance?.container,
1922
+ cardClassName: uiProps.appearance?.card
1923
+ },
1924
+ children: [
1925
+ /* @__PURE__ */ jsx(
1926
+ AuthHeader,
1927
+ {
1928
+ title: "Enter Reset Code",
1929
+ subtitle: `We've sent a 6-digit verification code to ${email}. Please enter it below to reset your password. The code will expire in 10 minutes.`,
1930
+ appearance: {
1931
+ containerClassName: uiProps.appearance?.header?.container,
1932
+ titleClassName: uiProps.appearance?.header?.title,
1933
+ subtitleClassName: uiProps.appearance?.header?.subtitle
1934
+ }
1935
+ }
1936
+ ),
1937
+ /* @__PURE__ */ jsx(AuthErrorBanner, { error, className: uiProps.appearance?.errorBanner }),
1938
+ /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col gap-6 items-center", children: [
1939
+ /* @__PURE__ */ jsx("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: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 mt-2", children: [
1940
+ /* @__PURE__ */ jsx(
1941
+ AuthVerificationCodeInput,
1942
+ {
1943
+ value: verificationCode,
1944
+ onChange: setVerificationCode,
1945
+ email,
1946
+ disabled: isVerifyingCode,
1947
+ onComplete: (code) => {
1948
+ void handleVerifyCode(code);
1949
+ }
1950
+ }
1951
+ ),
1952
+ isVerifyingCode && /* @__PURE__ */ jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 text-center", children: "Verifying..." })
1953
+ ] }) }),
1954
+ /* @__PURE__ */ jsxs("div", { className: "w-full text-sm text-center text-neutral-600 dark:text-neutral-400", children: [
1955
+ "Didn't receive the email?",
1956
+ " ",
1957
+ /* @__PURE__ */ jsx(
1958
+ "button",
1959
+ {
1960
+ onClick: () => {
1961
+ void handleResendCode();
1962
+ },
1963
+ disabled: resendDisabled || isSendingCode,
1964
+ className: "text-black dark:text-white font-medium transition-colors disabled:cursor-not-allowed cursor-pointer hover:underline disabled:no-underline disabled:opacity-50",
1965
+ children: isSendingCode ? "Sending..." : resendDisabled ? `Retry in (${resendCountdown}s)` : "Click to resend"
1966
+ }
1967
+ )
1968
+ ] })
1969
+ ] }),
1970
+ /* @__PURE__ */ jsx("p", { className: "text-center text-sm text-gray-600 dark:text-gray-400", children: /* @__PURE__ */ jsx("a", { href: backToSignInUrl, className: "text-black dark:text-white font-medium", children: "Back to Sign In" }) })
1971
+ ]
1972
+ }
1973
+ );
1974
+ }
1975
+ return /* @__PURE__ */ jsx(
1976
+ ResetPassword,
1977
+ {
1978
+ token: resetToken,
1979
+ backToSignInUrl,
1980
+ onSuccess: handlePasswordResetSuccess,
1981
+ onError,
1982
+ appearance: uiProps.appearance
1983
+ }
1984
+ );
1985
+ }
1986
+ function VerifyEmailStatus({
1987
+ status,
1988
+ error,
1989
+ appearance = {},
1990
+ verifyingTitle = "Verifying your email...",
1991
+ successTitle = "Email Verified!",
1992
+ successMessage = "Your email has been verified successfully. You can close this page and return to your app.",
1993
+ errorTitle = "Verification Failed"
1994
+ }) {
1995
+ if (status === "verifying") {
1996
+ return /* @__PURE__ */ jsx(AuthContainer, { appearance, children: /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col items-center justify-center gap-6", children: [
1997
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold text-black dark:text-white", children: verifyingTitle }),
1998
+ /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-black dark:border-white" })
1999
+ ] }) });
2000
+ }
2001
+ if (status === "error") {
2002
+ return /* @__PURE__ */ jsx(AuthContainer, { appearance, children: /* @__PURE__ */ jsx("div", { className: "w-full flex flex-col items-stretch justify-center gap-6", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start justify-center gap-2", children: [
2003
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold text-black dark:text-white", children: errorTitle }),
2004
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed", children: [
2005
+ error || "The verification link is invalid or has expired.",
2006
+ " Please try again or contact support if the problem persists. You can close this page and return to your app."
2007
+ ] })
2008
+ ] }) }) });
2009
+ }
2010
+ return /* @__PURE__ */ jsx(AuthContainer, { appearance, children: /* @__PURE__ */ jsx("div", { className: "w-full flex flex-col items-stretch justify-center gap-6", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
2011
+ /* @__PURE__ */ jsx("div", { className: "w-16 h-16 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center", children: /* @__PURE__ */ jsx("svg", { className: "w-8 h-8 text-green-600 dark:text-green-400", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M5 13l4 4L19 7" }) }) }),
2012
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold text-black dark:text-white text-center", children: successTitle }),
2013
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 text-center", children: successMessage })
2014
+ ] }) }) });
2015
+ }
2016
+ function VerifyEmail({
2017
+ token,
2018
+ onSuccess,
2019
+ onError,
2020
+ ...uiProps
2021
+ }) {
2022
+ const { verifyEmail } = useInsforge();
2023
+ const [status, setStatus] = useState("verifying");
2024
+ const [error, setError] = useState("");
2025
+ useEffect(() => {
2026
+ const verifyEmailFn = async () => {
2027
+ if (!token) {
2028
+ const errorMessage = "Invalid verification link. Missing required token.";
2029
+ setError(errorMessage);
2030
+ setStatus("error");
2031
+ if (onError) {
2032
+ onError(new Error(errorMessage));
2033
+ }
2034
+ return;
2035
+ }
2036
+ try {
2037
+ const result = await verifyEmail(token);
2038
+ if (!result?.accessToken) {
2039
+ const errorMessage = result ? "Verification succeeded but no access token received" : "Email verification failed";
2040
+ setError(errorMessage);
2041
+ setStatus("error");
2042
+ if (onError) {
2043
+ onError(new Error(errorMessage));
2044
+ }
2045
+ return;
2046
+ }
2047
+ setStatus("success");
2048
+ if (onSuccess) {
2049
+ onSuccess({
2050
+ accessToken: result.accessToken,
2051
+ user: result.user
2052
+ });
2053
+ }
2054
+ } catch (err) {
2055
+ const errorMessage = err.message || "Email verification failed";
2056
+ setError(errorMessage);
2057
+ setStatus("error");
2058
+ if (onError) {
2059
+ onError(new Error(errorMessage));
2060
+ }
2061
+ }
2062
+ };
2063
+ void verifyEmailFn();
2064
+ }, [token, verifyEmail, onSuccess, onError]);
2065
+ return /* @__PURE__ */ jsx(VerifyEmailStatus, { status, error, ...uiProps });
2066
+ }
2067
+ function UserButton({
2068
+ afterSignOutUrl = "/",
2069
+ mode = "detailed",
2070
+ appearance = {}
2071
+ }) {
2072
+ const { user, signOut } = useInsforge();
2073
+ const [isOpen, setIsOpen] = useState(false);
2074
+ const [imageError, setImageError] = useState(false);
2075
+ const dropdownRef = useRef(null);
2076
+ useEffect(() => {
2077
+ setImageError(false);
2078
+ const avatarUrl = user?.avatarUrl;
2079
+ if (!avatarUrl) return;
2080
+ const checkImageUrl = async () => {
2081
+ try {
2082
+ const response = await fetch(avatarUrl, {
2083
+ method: "HEAD",
2084
+ cache: "no-cache"
2085
+ });
2086
+ if (!response.ok) {
2087
+ setImageError(true);
2088
+ }
2089
+ } catch (error) {
2090
+ setImageError(true);
2091
+ }
2092
+ };
2093
+ checkImageUrl();
2094
+ }, [user?.avatarUrl]);
2095
+ useEffect(() => {
2096
+ function handleClickOutside(event) {
2097
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
2098
+ setIsOpen(false);
2099
+ }
2100
+ }
2101
+ if (isOpen) {
2102
+ document.addEventListener("mousedown", handleClickOutside);
2103
+ }
2104
+ return () => {
2105
+ document.removeEventListener("mousedown", handleClickOutside);
2106
+ };
2107
+ }, [isOpen]);
2108
+ async function handleSignOut() {
2109
+ await signOut();
2110
+ setIsOpen(false);
2111
+ window.location.href = afterSignOutUrl;
2112
+ }
2113
+ if (!user) return null;
2114
+ const initials = user.name ? user.name.charAt(0).toUpperCase() : user.email.split("@")[0].slice(0, 2).toUpperCase();
2115
+ return /* @__PURE__ */ jsxs(
2116
+ "div",
2117
+ {
2118
+ className: cn("relative inline-block", appearance.containerClassName),
2119
+ ref: dropdownRef,
2120
+ children: [
2121
+ /* @__PURE__ */ jsxs(
2122
+ "button",
2123
+ {
2124
+ className: cn(
2125
+ "p-1 bg-transparent border-0 rounded-full cursor-pointer transition-all duration-200",
2126
+ "flex items-center justify-center gap-2",
2127
+ "hover:bg-black/5",
2128
+ mode === "detailed" && "rounded-lg p-2",
2129
+ appearance.buttonClassName
2130
+ ),
2131
+ onClick: () => setIsOpen(!isOpen),
2132
+ "aria-expanded": isOpen,
2133
+ "aria-haspopup": "true",
2134
+ children: [
2135
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center w-10 h-10 bg-blue-500 rounded-full", children: user.avatarUrl && !imageError ? /* @__PURE__ */ jsx(
2136
+ "img",
2137
+ {
1281
2138
  src: user.avatarUrl,
1282
2139
  alt: user.email,
1283
2140
  onError: () => setImageError(true),
@@ -1444,268 +2301,7 @@ function InsforgeCallback({
1444
2301
  ] }) });
1445
2302
  return loadingComponent || defaultLoading;
1446
2303
  }
1447
- function ForgotPasswordForm({
1448
- email,
1449
- onEmailChange,
1450
- onSubmit,
1451
- error,
1452
- loading = false,
1453
- success = false,
1454
- appearance = {},
1455
- title = "Forgot Password?",
1456
- subtitle = "Enter your email address and we'll send you a code to reset your password.",
1457
- emailLabel = "Email",
1458
- emailPlaceholder = "example@email.com",
1459
- submitButtonText = "Send Reset Code",
1460
- loadingButtonText = "Sending...",
1461
- backToSignInText = "Remember your password?",
1462
- backToSignInUrl = "/sign-in",
1463
- successTitle = "Check Your Email",
1464
- successMessage
1465
- }) {
1466
- if (success) {
1467
- return /* @__PURE__ */ jsx(
1468
- AuthContainer,
1469
- {
1470
- appearance: {
1471
- containerClassName: appearance.container,
1472
- cardClassName: appearance.card
1473
- },
1474
- children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
1475
- /* @__PURE__ */ jsx("div", { className: "w-16 h-16 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center", children: /* @__PURE__ */ jsx("svg", { className: "w-8 h-8 text-green-600 dark:text-green-400", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M5 13l4 4L19 7" }) }) }),
1476
- /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold text-black dark:text-white text-center", children: successTitle }),
1477
- /* @__PURE__ */ jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 text-center", children: successMessage || `We've sent a password reset link to ${email}. Please check your email and follow the instructions.` }),
1478
- /* @__PURE__ */ jsx("a", { href: backToSignInUrl, className: "mt-4 text-black dark:text-white font-medium", children: "Back to Sign In" })
1479
- ] })
1480
- }
1481
- );
1482
- }
1483
- return /* @__PURE__ */ jsxs(
1484
- AuthContainer,
1485
- {
1486
- appearance: {
1487
- containerClassName: appearance.container,
1488
- cardClassName: appearance.card
1489
- },
1490
- children: [
1491
- /* @__PURE__ */ jsx(
1492
- AuthHeader,
1493
- {
1494
- title,
1495
- subtitle,
1496
- appearance: {
1497
- containerClassName: appearance.header?.container,
1498
- titleClassName: appearance.header?.title,
1499
- subtitleClassName: appearance.header?.subtitle
1500
- }
1501
- }
1502
- ),
1503
- /* @__PURE__ */ jsx(
1504
- AuthErrorBanner,
1505
- {
1506
- error: error || "",
1507
- className: appearance.errorBanner
1508
- }
1509
- ),
1510
- /* @__PURE__ */ jsxs(
1511
- "form",
1512
- {
1513
- onSubmit,
1514
- noValidate: true,
1515
- className: appearance.form?.container || "flex flex-col items-stretch justify-center gap-6",
1516
- children: [
1517
- /* @__PURE__ */ jsx(
1518
- AuthFormField,
1519
- {
1520
- id: "email",
1521
- type: "email",
1522
- label: emailLabel,
1523
- placeholder: emailPlaceholder,
1524
- value: email,
1525
- onChange: (e) => onEmailChange(e.target.value),
1526
- required: true,
1527
- autoComplete: "email",
1528
- appearance: {
1529
- containerClassName: appearance.form?.emailField?.container,
1530
- labelClassName: appearance.form?.emailField?.label,
1531
- inputClassName: appearance.form?.emailField?.input
1532
- }
1533
- }
1534
- ),
1535
- /* @__PURE__ */ jsx(
1536
- AuthSubmitButton,
1537
- {
1538
- isLoading: loading,
1539
- disabled: loading,
1540
- className: appearance.button,
1541
- children: loading ? loadingButtonText : submitButtonText
1542
- }
1543
- )
1544
- ]
1545
- }
1546
- ),
1547
- /* @__PURE__ */ jsx(
1548
- AuthLink,
1549
- {
1550
- text: backToSignInText,
1551
- linkText: "Back to Sign In",
1552
- href: backToSignInUrl,
1553
- appearance: {
1554
- containerClassName: appearance.link?.container,
1555
- linkClassName: appearance.link?.link
1556
- }
1557
- }
1558
- )
1559
- ]
1560
- }
1561
- );
1562
- }
1563
- function ResetPasswordForm({
1564
- newPassword,
1565
- confirmPassword,
1566
- onNewPasswordChange,
1567
- onConfirmPasswordChange,
1568
- onSubmit,
1569
- error,
1570
- loading = false,
1571
- emailAuthConfig,
1572
- appearance = {},
1573
- title = "Reset Password",
1574
- subtitle = "Enter your new password below.",
1575
- newPasswordLabel = "New Password",
1576
- newPasswordPlaceholder = "\u2022\u2022\u2022\u2022\u2022\u2022",
1577
- confirmPasswordLabel = "Confirm Password",
1578
- confirmPasswordPlaceholder = "\u2022\u2022\u2022\u2022\u2022\u2022",
1579
- submitButtonText = "Reset Password",
1580
- loadingButtonText = "Resetting...",
1581
- backToSignInText = "",
1582
- backToSignInUrl = "/sign-in"
1583
- }) {
1584
- return /* @__PURE__ */ jsxs(
1585
- AuthContainer,
1586
- {
1587
- appearance: {
1588
- containerClassName: appearance.container,
1589
- cardClassName: appearance.card
1590
- },
1591
- children: [
1592
- /* @__PURE__ */ jsx(
1593
- AuthHeader,
1594
- {
1595
- title,
1596
- subtitle,
1597
- appearance: {
1598
- containerClassName: appearance.header?.container,
1599
- titleClassName: appearance.header?.title,
1600
- subtitleClassName: appearance.header?.subtitle
1601
- }
1602
- }
1603
- ),
1604
- /* @__PURE__ */ jsx(
1605
- AuthErrorBanner,
1606
- {
1607
- error: error || "",
1608
- className: appearance.errorBanner
1609
- }
1610
- ),
1611
- /* @__PURE__ */ jsxs(
1612
- "form",
1613
- {
1614
- onSubmit,
1615
- noValidate: true,
1616
- className: appearance.form?.container || "flex flex-col items-stretch justify-center gap-6",
1617
- children: [
1618
- /* @__PURE__ */ jsx(
1619
- AuthPasswordField,
1620
- {
1621
- id: "newPassword",
1622
- label: newPasswordLabel,
1623
- placeholder: newPasswordPlaceholder,
1624
- value: newPassword,
1625
- onChange: (e) => onNewPasswordChange(e.target.value),
1626
- required: true,
1627
- autoComplete: "new-password",
1628
- showStrengthIndicator: true,
1629
- emailAuthConfig,
1630
- appearance: {
1631
- containerClassName: appearance.form?.newPasswordField?.container,
1632
- labelClassName: appearance.form?.newPasswordField?.label,
1633
- inputClassName: appearance.form?.newPasswordField?.input
1634
- }
1635
- }
1636
- ),
1637
- /* @__PURE__ */ jsx(
1638
- AuthPasswordField,
1639
- {
1640
- id: "confirmPassword",
1641
- label: confirmPasswordLabel,
1642
- placeholder: confirmPasswordPlaceholder,
1643
- value: confirmPassword,
1644
- onChange: (e) => onConfirmPasswordChange(e.target.value),
1645
- required: true,
1646
- autoComplete: "new-password",
1647
- emailAuthConfig,
1648
- appearance: {
1649
- containerClassName: appearance.form?.confirmPasswordField?.container,
1650
- labelClassName: appearance.form?.confirmPasswordField?.label,
1651
- inputClassName: appearance.form?.confirmPasswordField?.input
1652
- }
1653
- }
1654
- ),
1655
- /* @__PURE__ */ jsx(
1656
- AuthSubmitButton,
1657
- {
1658
- isLoading: loading,
1659
- disabled: loading,
1660
- className: appearance.button,
1661
- children: loading ? loadingButtonText : submitButtonText
1662
- }
1663
- )
1664
- ]
1665
- }
1666
- ),
1667
- /* @__PURE__ */ jsxs("p", { className: appearance.backToSignIn || "text-center text-sm text-gray-600 dark:text-gray-400", children: [
1668
- backToSignInText && /* @__PURE__ */ jsxs("span", { children: [
1669
- backToSignInText,
1670
- " "
1671
- ] }),
1672
- /* @__PURE__ */ jsx("a", { href: backToSignInUrl, className: "text-black dark:text-white font-medium", children: "Back to Sign In" })
1673
- ] })
1674
- ]
1675
- }
1676
- );
1677
- }
1678
- function VerifyEmailStatus({
1679
- status,
1680
- error,
1681
- appearance = {},
1682
- verifyingTitle = "Verifying your email...",
1683
- successTitle = "Email Verified!",
1684
- successMessage = "Your email has been verified successfully. You can close this page and return to your app.",
1685
- errorTitle = "Verification Failed"
1686
- }) {
1687
- if (status === "verifying") {
1688
- return /* @__PURE__ */ jsx(AuthContainer, { appearance, children: /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col items-center justify-center gap-6", children: [
1689
- /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold text-black dark:text-white", children: verifyingTitle }),
1690
- /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-black dark:border-white" })
1691
- ] }) });
1692
- }
1693
- if (status === "error") {
1694
- return /* @__PURE__ */ jsx(AuthContainer, { appearance, children: /* @__PURE__ */ jsx("div", { className: "w-full flex flex-col items-stretch justify-center gap-6", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start justify-center gap-2", children: [
1695
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold text-black dark:text-white", children: errorTitle }),
1696
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed", children: [
1697
- error || "The verification link is invalid or has expired.",
1698
- " Please try again or contact support if the problem persists. You can close this page and return to your app."
1699
- ] })
1700
- ] }) }) });
1701
- }
1702
- return /* @__PURE__ */ jsx(AuthContainer, { appearance, children: /* @__PURE__ */ jsx("div", { className: "w-full flex flex-col items-stretch justify-center gap-6", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
1703
- /* @__PURE__ */ jsx("div", { className: "w-16 h-16 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center", children: /* @__PURE__ */ jsx("svg", { className: "w-8 h-8 text-green-600 dark:text-green-400", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M5 13l4 4L19 7" }) }) }),
1704
- /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold text-black dark:text-white text-center", children: successTitle }),
1705
- /* @__PURE__ */ jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 text-center", children: successMessage })
1706
- ] }) }) });
1707
- }
1708
2304
 
1709
- export { AuthBranding, AuthContainer, AuthDivider, AuthErrorBanner, AuthFormField, AuthHeader, AuthLink, AuthOAuthButton, AuthOAuthProviders, AuthPasswordField, AuthPasswordStrengthIndicator, AuthSubmitButton, AuthVerificationCodeInput, ForgotPasswordForm, InsforgeCallback, Protect, ResetPasswordForm, SignIn, SignInForm, SignUp, SignUpForm, SignedIn, SignedOut, UserButton, VerifyEmailStatus, validatePasswordStrength };
2305
+ export { AuthBranding, AuthContainer, AuthDivider, AuthEmailVerificationStep, AuthErrorBanner, AuthFormField, AuthHeader, AuthLink, AuthOAuthButton, AuthOAuthProviders, AuthPasswordField, AuthPasswordStrengthIndicator, AuthSubmitButton, AuthVerificationCodeInput, ForgotPassword, ForgotPasswordForm, InsforgeCallback, Protect, ResetPassword, ResetPasswordForm, SignIn, SignInForm, SignUp, SignUpForm, SignedIn, SignedOut, UserButton, VerifyEmail, VerifyEmailStatus, validatePasswordStrength };
1710
2306
  //# sourceMappingURL=components.mjs.map
1711
2307
  //# sourceMappingURL=components.mjs.map