@insforge/react 0.2.9 → 0.3.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.
@@ -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,126 @@ function AuthVerificationCodeInput({
756
764
  )) })
757
765
  ] });
758
766
  }
767
+ function AuthEmailVerificationStep({
768
+ email,
769
+ title = "Verify Your Email",
770
+ description,
771
+ method = "code",
772
+ onVerifyCode
773
+ }) {
774
+ const { baseUrl } = useInsforge();
775
+ const [insforge] = useState(() => createClient({ baseUrl }));
776
+ const [resendDisabled, setResendDisabled] = useState(true);
777
+ const [resendCountdown, setResendCountdown] = useState(60);
778
+ const [isSending, setIsSending] = useState(false);
779
+ const [verificationCode, setVerificationCode] = useState("");
780
+ const [isVerifying, setIsVerifying] = useState(false);
781
+ const [verificationError, setVerificationError] = useState("");
782
+ 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.";
783
+ useEffect(() => {
784
+ const sendInitialEmail = async () => {
785
+ try {
786
+ if (method === "code") {
787
+ await insforge.auth.sendVerificationCode({ email });
788
+ } else {
789
+ await insforge.auth.sendVerificationLink({ email });
790
+ }
791
+ } catch {
792
+ }
793
+ };
794
+ void sendInitialEmail();
795
+ }, [email, method, insforge.auth]);
796
+ useEffect(() => {
797
+ if (resendCountdown > 0) {
798
+ const timer = setInterval(() => {
799
+ setResendCountdown((prev) => {
800
+ if (prev <= 1) {
801
+ setResendDisabled(false);
802
+ return 0;
803
+ }
804
+ return prev - 1;
805
+ });
806
+ }, 1e3);
807
+ return () => clearInterval(timer);
808
+ }
809
+ }, [resendCountdown]);
810
+ const handleResend = async () => {
811
+ setResendDisabled(true);
812
+ setResendCountdown(60);
813
+ setIsSending(true);
814
+ setVerificationError("");
815
+ try {
816
+ if (method === "code") {
817
+ await insforge.auth.sendVerificationCode({ email });
818
+ } else {
819
+ await insforge.auth.sendVerificationLink({ email });
820
+ }
821
+ } catch {
822
+ setResendDisabled(false);
823
+ setResendCountdown(0);
824
+ } finally {
825
+ setIsSending(false);
826
+ }
827
+ };
828
+ const handleVerifyCode = async (code) => {
829
+ if (!onVerifyCode) {
830
+ return;
831
+ }
832
+ setIsVerifying(true);
833
+ setVerificationError("");
834
+ try {
835
+ await onVerifyCode(code);
836
+ } catch (error) {
837
+ setVerificationError(
838
+ error instanceof Error ? error.message : "Invalid verification code. Please try again."
839
+ );
840
+ setVerificationCode("");
841
+ } finally {
842
+ setIsVerifying(false);
843
+ }
844
+ };
845
+ const displayDescription = description || defaultDescription;
846
+ return /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col gap-6 items-center", children: [
847
+ /* @__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: [
848
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-black dark:text-white", children: title }),
849
+ /* @__PURE__ */ jsx("p", { className: "text-neutral-600 dark:text-neutral-400 text-sm leading-relaxed", children: displayDescription.split("{email}").map((part, index, array) => /* @__PURE__ */ jsxs("span", { children: [
850
+ part,
851
+ index < array.length - 1 && /* @__PURE__ */ jsx("span", { className: "font-medium text-black dark:text-white", children: email })
852
+ ] }, index)) }),
853
+ method === "code" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 mt-2", children: [
854
+ /* @__PURE__ */ jsx(
855
+ AuthVerificationCodeInput,
856
+ {
857
+ value: verificationCode,
858
+ onChange: setVerificationCode,
859
+ email,
860
+ disabled: isVerifying,
861
+ onComplete: (code) => {
862
+ void handleVerifyCode(code);
863
+ }
864
+ }
865
+ ),
866
+ verificationError && /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600 dark:text-red-400 text-center", children: verificationError }),
867
+ isVerifying && /* @__PURE__ */ jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 text-center", children: "Verifying..." })
868
+ ] })
869
+ ] }),
870
+ /* @__PURE__ */ jsxs("div", { className: "w-full text-sm text-center text-neutral-600 dark:text-neutral-400", children: [
871
+ "Didn't receive the email?",
872
+ " ",
873
+ /* @__PURE__ */ jsx(
874
+ "button",
875
+ {
876
+ onClick: () => {
877
+ void handleResend();
878
+ },
879
+ disabled: resendDisabled || isSending,
880
+ className: "text-black dark:text-white font-medium transition-colors disabled:cursor-not-allowed cursor-pointer hover:underline disabled:no-underline disabled:opacity-50",
881
+ children: isSending ? "Sending..." : resendDisabled ? `Retry in (${resendCountdown}s)` : "Click to resend"
882
+ }
883
+ )
884
+ ] })
885
+ ] });
886
+ }
759
887
  function SignInForm({
760
888
  email,
761
889
  password,
@@ -920,6 +1048,7 @@ function SignIn({
920
1048
  const [password, setPassword] = useState("");
921
1049
  const [error, setError] = useState("");
922
1050
  const [loading, setLoading] = useState(false);
1051
+ const [step, setStep] = useState("form");
923
1052
  const [oauthLoading, setOauthLoading] = useState(
924
1053
  null
925
1054
  );
@@ -931,6 +1060,11 @@ function SignIn({
931
1060
  try {
932
1061
  const result = await signIn(email, password);
933
1062
  if ("error" in result) {
1063
+ if (result.statusCode === 403) {
1064
+ setStep("awaiting-verification");
1065
+ setLoading(false);
1066
+ return;
1067
+ }
934
1068
  throw new Error(result.error);
935
1069
  }
936
1070
  const { user, accessToken } = result;
@@ -945,6 +1079,21 @@ function SignIn({
945
1079
  setLoading(false);
946
1080
  }
947
1081
  }
1082
+ async function handleVerifyCode(code) {
1083
+ try {
1084
+ const result = await insforge.auth.verifyEmail({ email, otp: code });
1085
+ if (result.error) {
1086
+ throw new Error(result.error.message || "Verification failed");
1087
+ }
1088
+ if (result.data?.accessToken) {
1089
+ if (onSuccess && result.data.user) {
1090
+ onSuccess(result.data.user, result.data.accessToken);
1091
+ }
1092
+ }
1093
+ } catch (err) {
1094
+ throw new Error(err.message || "Invalid verification code");
1095
+ }
1096
+ }
948
1097
  async function handleOAuth(provider) {
949
1098
  try {
950
1099
  setOauthLoading(provider);
@@ -963,7 +1112,7 @@ function SignIn({
963
1112
  if (!emailConfig) {
964
1113
  return null;
965
1114
  }
966
- return /* @__PURE__ */ jsx(
1115
+ return step === "form" ? /* @__PURE__ */ jsx(
967
1116
  SignInForm,
968
1117
  {
969
1118
  email,
@@ -979,6 +1128,12 @@ function SignIn({
979
1128
  emailAuthConfig: emailConfig,
980
1129
  ...uiProps
981
1130
  }
1131
+ ) : /* @__PURE__ */ jsx(
1132
+ AuthEmailVerificationStep,
1133
+ {
1134
+ email,
1135
+ onVerifyCode: handleVerifyCode
1136
+ }
982
1137
  );
983
1138
  }
984
1139
  function SignUpForm({
@@ -1129,6 +1284,34 @@ function SignUpForm({
1129
1284
  }
1130
1285
  );
1131
1286
  }
1287
+ var emailSchema = z.string().min(1, "Email is required").email("Invalid email address");
1288
+ function createPasswordSchema(options) {
1289
+ const {
1290
+ minLength = 6,
1291
+ requireUppercase = false,
1292
+ requireLowercase = false,
1293
+ requireNumber = false,
1294
+ requireSpecialChar = false
1295
+ } = options || {};
1296
+ let schema = z.string().min(minLength, `Password must be at least ${minLength} characters`);
1297
+ if (requireUppercase) {
1298
+ schema = schema.regex(/[A-Z]/, "Password must contain at least one uppercase letter");
1299
+ }
1300
+ if (requireLowercase) {
1301
+ schema = schema.regex(/[a-z]/, "Password must contain at least one lowercase letter");
1302
+ }
1303
+ if (requireNumber) {
1304
+ schema = schema.regex(/\d/, "Password must contain at least one number");
1305
+ }
1306
+ if (requireSpecialChar) {
1307
+ schema = schema.regex(
1308
+ /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/,
1309
+ "Password must contain at least one special character"
1310
+ );
1311
+ }
1312
+ return schema;
1313
+ }
1314
+ createPasswordSchema();
1132
1315
  function SignUp({
1133
1316
  afterSignUpUrl,
1134
1317
  onSuccess,
@@ -1141,6 +1324,7 @@ function SignUp({
1141
1324
  const [password, setPassword] = useState("");
1142
1325
  const [error, setError] = useState("");
1143
1326
  const [loading, setLoading] = useState(false);
1327
+ const [step, setStep] = useState("form");
1144
1328
  const [oauthLoading, setOauthLoading] = useState(
1145
1329
  null
1146
1330
  );
@@ -1149,19 +1333,46 @@ function SignUp({
1149
1333
  e.preventDefault();
1150
1334
  setLoading(true);
1151
1335
  setError("");
1152
- if (emailConfig && !validatePasswordStrength(password, emailConfig)) {
1153
- setError("Password does not meet all requirements");
1336
+ if (!emailConfig) {
1337
+ setError("Configuration not loaded. Please refresh the page.");
1338
+ setLoading(false);
1339
+ return;
1340
+ }
1341
+ const emailValidation = emailSchema.safeParse(email);
1342
+ if (!emailValidation.success) {
1343
+ const firstError = emailValidation.error.issues[0];
1344
+ setError(firstError.message);
1345
+ setLoading(false);
1346
+ return;
1347
+ }
1348
+ const passwordZodSchema = createPasswordSchema({
1349
+ minLength: emailConfig.passwordMinLength,
1350
+ requireUppercase: emailConfig.requireUppercase,
1351
+ requireLowercase: emailConfig.requireLowercase,
1352
+ requireNumber: emailConfig.requireNumber,
1353
+ requireSpecialChar: emailConfig.requireSpecialChar
1354
+ });
1355
+ const passwordValidation = passwordZodSchema.safeParse(password);
1356
+ if (!passwordValidation.success) {
1357
+ const firstError = passwordValidation.error.issues[0];
1358
+ setError(firstError.message);
1154
1359
  setLoading(false);
1155
1360
  return;
1156
1361
  }
1157
1362
  try {
1158
- const result = await signUp(email, password);
1363
+ const result = await signUp(emailValidation.data, password);
1159
1364
  if ("error" in result) {
1160
1365
  throw new Error(result.error);
1161
1366
  }
1162
- const { user, accessToken } = result;
1163
- if (onSuccess) {
1164
- if (user) onSuccess(user, accessToken || "");
1367
+ if (result.requiresEmailVerification && !result.accessToken) {
1368
+ setStep("awaiting-verification");
1369
+ setLoading(false);
1370
+ return;
1371
+ }
1372
+ if (result.accessToken && result.user) {
1373
+ if (onSuccess) {
1374
+ onSuccess(result.user, result.accessToken);
1375
+ }
1165
1376
  }
1166
1377
  } catch (err) {
1167
1378
  const errorMessage = err.message || "Sign up failed";
@@ -1171,6 +1382,21 @@ function SignUp({
1171
1382
  setLoading(false);
1172
1383
  }
1173
1384
  }
1385
+ async function handleVerifyCode(code) {
1386
+ try {
1387
+ const result = await insforge.auth.verifyEmail({ email, otp: code });
1388
+ if (result.error) {
1389
+ throw new Error(result.error.message || "Verification failed");
1390
+ }
1391
+ if (result.data?.accessToken) {
1392
+ if (onSuccess && result.data.user) {
1393
+ onSuccess(result.data.user, result.data.accessToken);
1394
+ }
1395
+ }
1396
+ } catch (err) {
1397
+ throw new Error(err.message || "Invalid verification code");
1398
+ }
1399
+ }
1174
1400
  async function handleOAuth(provider) {
1175
1401
  try {
1176
1402
  setOauthLoading(provider);
@@ -1189,7 +1415,7 @@ function SignUp({
1189
1415
  if (!emailConfig) {
1190
1416
  return null;
1191
1417
  }
1192
- return /* @__PURE__ */ jsx(
1418
+ return step === "form" ? /* @__PURE__ */ jsx(
1193
1419
  SignUpForm,
1194
1420
  {
1195
1421
  email,
@@ -1205,326 +1431,75 @@ function SignUp({
1205
1431
  emailAuthConfig: emailConfig,
1206
1432
  ...uiProps
1207
1433
  }
1434
+ ) : /* @__PURE__ */ jsx(
1435
+ AuthEmailVerificationStep,
1436
+ {
1437
+ email,
1438
+ onVerifyCode: handleVerifyCode
1439
+ }
1208
1440
  );
1209
1441
  }
1210
- function UserButton({
1211
- afterSignOutUrl = "/",
1212
- mode = "detailed",
1213
- appearance = {}
1442
+ function ForgotPasswordForm({
1443
+ email,
1444
+ onEmailChange,
1445
+ onSubmit,
1446
+ error,
1447
+ loading = false,
1448
+ success = false,
1449
+ appearance = {},
1450
+ title = "Forgot Password?",
1451
+ subtitle = "Enter your email address and we'll send you a code to reset your password.",
1452
+ emailLabel = "Email",
1453
+ emailPlaceholder = "example@email.com",
1454
+ submitButtonText = "Send Reset Code",
1455
+ loadingButtonText = "Sending...",
1456
+ backToSignInText = "Remember your password?",
1457
+ backToSignInUrl = "/sign-in",
1458
+ successTitle = "Check Your Email",
1459
+ successMessage
1214
1460
  }) {
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);
1461
+ if (success) {
1462
+ return /* @__PURE__ */ jsx(
1463
+ AuthContainer,
1464
+ {
1465
+ appearance: {
1466
+ containerClassName: appearance.container,
1467
+ cardClassName: appearance.card
1468
+ },
1469
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
1470
+ /* @__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" }) }) }),
1471
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold text-black dark:text-white text-center", children: successTitle }),
1472
+ /* @__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.` }),
1473
+ /* @__PURE__ */ jsx("a", { href: backToSignInUrl, className: "mt-4 text-black dark:text-white font-medium", children: "Back to Sign In" })
1474
+ ] })
1242
1475
  }
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;
1476
+ );
1255
1477
  }
1256
- if (!user) return null;
1257
- const initials = user.name ? user.name.charAt(0).toUpperCase() : user.email.split("@")[0].slice(0, 2).toUpperCase();
1258
1478
  return /* @__PURE__ */ jsxs(
1259
- "div",
1479
+ AuthContainer,
1260
1480
  {
1261
- className: cn("relative inline-block", appearance.containerClassName),
1262
- ref: dropdownRef,
1481
+ appearance: {
1482
+ containerClassName: appearance.container,
1483
+ cardClassName: appearance.card
1484
+ },
1263
1485
  children: [
1264
- /* @__PURE__ */ jsxs(
1265
- "button",
1486
+ /* @__PURE__ */ jsx(
1487
+ AuthHeader,
1266
1488
  {
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",
1277
- 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
- {
1281
- src: user.avatarUrl,
1282
- alt: user.email,
1283
- onError: () => setImageError(true),
1284
- className: "rounded-full object-cover w-full h-full"
1285
- }
1286
- ) : /* @__PURE__ */ jsx("span", { className: "text-white font-semibold text-sm", children: initials }) }),
1287
- mode === "detailed" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-0.5", children: [
1288
- user.name && /* @__PURE__ */ jsx(
1289
- "div",
1290
- {
1291
- className: cn(
1292
- "text-sm font-semibold text-gray-900 leading-5 text-left",
1293
- appearance.nameClassName
1294
- ),
1295
- children: user.name
1296
- }
1297
- ),
1298
- /* @__PURE__ */ jsx(
1299
- "div",
1300
- {
1301
- className: cn(
1302
- "text-xs text-gray-500 leading-4 text-left",
1303
- appearance.emailClassName
1304
- ),
1305
- children: user.email
1306
- }
1307
- )
1308
- ] })
1309
- ]
1489
+ title,
1490
+ subtitle,
1491
+ appearance: {
1492
+ containerClassName: appearance.header?.container,
1493
+ titleClassName: appearance.header?.title,
1494
+ subtitleClassName: appearance.header?.subtitle
1495
+ }
1310
1496
  }
1311
1497
  ),
1312
- isOpen && /* @__PURE__ */ jsx(
1313
- "div",
1498
+ /* @__PURE__ */ jsx(
1499
+ AuthErrorBanner,
1314
1500
  {
1315
- className: cn(
1316
- "absolute top-full right-0 mt-2 min-w-40",
1317
- "bg-white border border-gray-200 rounded-lg",
1318
- "shadow-lg z-50 overflow-hidden p-1",
1319
- appearance.dropdownClassName
1320
- ),
1321
- children: /* @__PURE__ */ jsxs(
1322
- "button",
1323
- {
1324
- onClick: handleSignOut,
1325
- className: "flex items-center justify-start gap-2 w-full px-3 py-2 text-sm font-normal text-red-600 bg-transparent border-0 rounded-md cursor-pointer transition-colors hover:bg-red-50 text-left",
1326
- children: [
1327
- /* @__PURE__ */ jsx(LogOut, { className: "w-5 h-5" }),
1328
- "Sign out"
1329
- ]
1330
- }
1331
- )
1332
- }
1333
- )
1334
- ]
1335
- }
1336
- );
1337
- }
1338
- function Protect({
1339
- children,
1340
- fallback,
1341
- redirectTo = "/sign-in",
1342
- condition,
1343
- onRedirect
1344
- }) {
1345
- const { isSignedIn, isLoaded, user } = useInsforge();
1346
- useEffect(() => {
1347
- if (isLoaded && !isSignedIn) {
1348
- if (onRedirect) {
1349
- onRedirect(redirectTo);
1350
- } else {
1351
- window.location.href = redirectTo;
1352
- }
1353
- } else if (isLoaded && isSignedIn && condition && user) {
1354
- if (!condition(user)) {
1355
- if (onRedirect) {
1356
- onRedirect(redirectTo);
1357
- } else {
1358
- window.location.href = redirectTo;
1359
- }
1360
- }
1361
- }
1362
- }, [isLoaded, isSignedIn, redirectTo, condition, user, onRedirect]);
1363
- if (!isLoaded) {
1364
- return fallback || /* @__PURE__ */ jsx("div", { className: "insforge-loading", children: "Loading..." });
1365
- }
1366
- if (!isSignedIn) {
1367
- return fallback || null;
1368
- }
1369
- if (condition && user && !condition(user)) {
1370
- return fallback || null;
1371
- }
1372
- return /* @__PURE__ */ jsx(Fragment, { children });
1373
- }
1374
- function SignedIn({ children }) {
1375
- const { isSignedIn, isLoaded } = useInsforge();
1376
- if (!isLoaded) return null;
1377
- if (!isSignedIn) return null;
1378
- return /* @__PURE__ */ jsx(Fragment, { children });
1379
- }
1380
- function SignedOut({ children }) {
1381
- const { isSignedIn, isLoaded } = useInsforge();
1382
- if (!isLoaded) return null;
1383
- if (isSignedIn) return null;
1384
- return /* @__PURE__ */ jsx(Fragment, { children });
1385
- }
1386
- function InsforgeCallback({
1387
- redirectTo,
1388
- onSuccess,
1389
- onError,
1390
- loadingComponent,
1391
- onRedirect
1392
- }) {
1393
- const isProcessingRef = useRef(false);
1394
- const { handleAuthCallback } = useInsforge();
1395
- useEffect(() => {
1396
- const processCallback = async () => {
1397
- if (isProcessingRef.current) return;
1398
- isProcessingRef.current = true;
1399
- const searchParams = new URLSearchParams(window.location.search);
1400
- const error = searchParams.get("error");
1401
- if (error) {
1402
- if (onError) {
1403
- onError(error);
1404
- } else {
1405
- const errorUrl = "/?error=" + encodeURIComponent(error);
1406
- if (onRedirect) {
1407
- onRedirect(errorUrl);
1408
- } else {
1409
- window.location.href = errorUrl;
1410
- }
1411
- }
1412
- return;
1413
- }
1414
- const accessToken = searchParams.get("access_token");
1415
- if (!accessToken) {
1416
- const errorMsg = "no_token";
1417
- if (onError) {
1418
- onError(errorMsg);
1419
- } else {
1420
- const errorUrl = "/?error=" + encodeURIComponent(errorMsg);
1421
- if (onRedirect) {
1422
- onRedirect(errorUrl);
1423
- } else {
1424
- window.location.href = errorUrl;
1425
- }
1426
- }
1427
- return;
1428
- }
1429
- const result = await handleAuthCallback({
1430
- accessToken
1431
- });
1432
- if (!result.success) {
1433
- const errorMsg = result.error || "authentication_failed";
1434
- if (onError) {
1435
- onError(errorMsg);
1436
- } else {
1437
- const errorUrl = "/?error=" + encodeURIComponent(errorMsg);
1438
- if (onRedirect) {
1439
- onRedirect(errorUrl);
1440
- } else {
1441
- window.location.href = errorUrl;
1442
- }
1443
- }
1444
- return;
1445
- }
1446
- window.history.replaceState({}, "", window.location.pathname);
1447
- if (onSuccess) {
1448
- onSuccess();
1449
- }
1450
- const destination = redirectTo || sessionStorage.getItem("auth_destination") || sessionStorage.getItem("oauth_final_destination") || "/";
1451
- sessionStorage.removeItem("auth_destination");
1452
- sessionStorage.removeItem("oauth_final_destination");
1453
- if (onRedirect) {
1454
- onRedirect(destination);
1455
- } else {
1456
- window.location.href = destination;
1457
- }
1458
- };
1459
- processCallback();
1460
- }, [handleAuthCallback, redirectTo, onSuccess, onError, onRedirect]);
1461
- const defaultLoading = /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
1462
- /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold mb-4", children: "Completing authentication..." }),
1463
- /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto" })
1464
- ] }) });
1465
- return loadingComponent || defaultLoading;
1466
- }
1467
- function ForgotPasswordForm({
1468
- email,
1469
- onEmailChange,
1470
- onSubmit,
1471
- error,
1472
- loading = false,
1473
- success = false,
1474
- appearance = {},
1475
- title = "Forgot Password?",
1476
- subtitle = "Enter your email address and we'll send you a code to reset your password.",
1477
- emailLabel = "Email",
1478
- emailPlaceholder = "example@email.com",
1479
- submitButtonText = "Send Reset Code",
1480
- loadingButtonText = "Sending...",
1481
- backToSignInText = "Remember your password?",
1482
- backToSignInUrl = "/sign-in",
1483
- successTitle = "Check Your Email",
1484
- successMessage
1485
- }) {
1486
- if (success) {
1487
- return /* @__PURE__ */ jsx(
1488
- AuthContainer,
1489
- {
1490
- appearance: {
1491
- containerClassName: appearance.container,
1492
- cardClassName: appearance.card
1493
- },
1494
- children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
1495
- /* @__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" }) }) }),
1496
- /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold text-black dark:text-white text-center", children: successTitle }),
1497
- /* @__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.` }),
1498
- /* @__PURE__ */ jsx("a", { href: backToSignInUrl, className: "mt-4 text-black dark:text-white font-medium", children: "Back to Sign In" })
1499
- ] })
1500
- }
1501
- );
1502
- }
1503
- return /* @__PURE__ */ jsxs(
1504
- AuthContainer,
1505
- {
1506
- appearance: {
1507
- containerClassName: appearance.container,
1508
- cardClassName: appearance.card
1509
- },
1510
- children: [
1511
- /* @__PURE__ */ jsx(
1512
- AuthHeader,
1513
- {
1514
- title,
1515
- subtitle,
1516
- appearance: {
1517
- containerClassName: appearance.header?.container,
1518
- titleClassName: appearance.header?.title,
1519
- subtitleClassName: appearance.header?.subtitle
1520
- }
1521
- }
1522
- ),
1523
- /* @__PURE__ */ jsx(
1524
- AuthErrorBanner,
1525
- {
1526
- error: error || "",
1527
- className: appearance.errorBanner
1501
+ error: error || "",
1502
+ className: appearance.errorBanner
1528
1503
  }
1529
1504
  ),
1530
1505
  /* @__PURE__ */ jsxs(
@@ -1695,37 +1670,616 @@ function ResetPasswordForm({
1695
1670
  }
1696
1671
  );
1697
1672
  }
1698
- function VerifyEmailStatus({
1699
- status,
1700
- error,
1701
- appearance = {},
1702
- verifyingTitle = "Verifying your email...",
1703
- successTitle = "Email Verified!",
1704
- successMessage = "Your email has been verified successfully. You can close this page and return to your app.",
1705
- errorTitle = "Verification Failed"
1673
+ function ResetPassword({
1674
+ token,
1675
+ backToSignInUrl = "/sign-in",
1676
+ onSuccess,
1677
+ onError,
1678
+ ...uiProps
1706
1679
  }) {
1707
- if (status === "verifying") {
1708
- return /* @__PURE__ */ jsx(AuthContainer, { appearance, children: /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col items-center justify-center gap-6", children: [
1709
- /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold text-black dark:text-white", children: verifyingTitle }),
1710
- /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-black dark:border-white" })
1711
- ] }) });
1680
+ const { resetPassword } = useInsforge();
1681
+ const { emailConfig } = usePublicAuthConfig();
1682
+ const [newPassword, setNewPassword] = useState("");
1683
+ const [confirmPassword, setConfirmPassword] = useState("");
1684
+ const [error, setError] = useState("");
1685
+ const [loading, setLoading] = useState(false);
1686
+ async function handleSubmit(e) {
1687
+ e.preventDefault();
1688
+ setLoading(true);
1689
+ setError("");
1690
+ if (!emailConfig) {
1691
+ setError("Configuration not loaded. Please refresh the page.");
1692
+ setLoading(false);
1693
+ return;
1694
+ }
1695
+ if (newPassword !== confirmPassword) {
1696
+ setError("Passwords do not match");
1697
+ setLoading(false);
1698
+ return;
1699
+ }
1700
+ if (!token) {
1701
+ setError("Reset token is missing");
1702
+ setLoading(false);
1703
+ return;
1704
+ }
1705
+ const passwordZodSchema = createPasswordSchema({
1706
+ minLength: emailConfig.passwordMinLength,
1707
+ requireUppercase: emailConfig.requireUppercase,
1708
+ requireLowercase: emailConfig.requireLowercase,
1709
+ requireNumber: emailConfig.requireNumber,
1710
+ requireSpecialChar: emailConfig.requireSpecialChar
1711
+ });
1712
+ const passwordValidation = passwordZodSchema.safeParse(newPassword);
1713
+ if (!passwordValidation.success) {
1714
+ const firstError = passwordValidation.error.issues[0];
1715
+ setError(firstError.message);
1716
+ setLoading(false);
1717
+ return;
1718
+ }
1719
+ try {
1720
+ const result = await resetPassword(token, newPassword);
1721
+ if (result?.message) {
1722
+ if (onSuccess) {
1723
+ onSuccess(result.redirectTo);
1724
+ }
1725
+ } else {
1726
+ const errorMessage = "Failed to reset password";
1727
+ setError(errorMessage);
1728
+ if (onError) {
1729
+ onError(new Error(errorMessage));
1730
+ }
1731
+ }
1732
+ } catch (err) {
1733
+ const errorMessage = err.message || "Failed to reset password";
1734
+ setError(errorMessage);
1735
+ if (onError) {
1736
+ onError(new Error(errorMessage));
1737
+ }
1738
+ } finally {
1739
+ setLoading(false);
1740
+ }
1712
1741
  }
1713
- if (status === "error") {
1714
- 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: [
1715
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold text-black dark:text-white", children: errorTitle }),
1716
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed", children: [
1717
- error || "The verification link is invalid or has expired.",
1718
- " Please try again or contact support if the problem persists. You can close this page and return to your app."
1719
- ] })
1720
- ] }) }) });
1742
+ if (!emailConfig) {
1743
+ return null;
1721
1744
  }
1722
- 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: [
1723
- /* @__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" }) }) }),
1724
- /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold text-black dark:text-white text-center", children: successTitle }),
1725
- /* @__PURE__ */ jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 text-center", children: successMessage })
1726
- ] }) }) });
1745
+ return /* @__PURE__ */ jsx(
1746
+ ResetPasswordForm,
1747
+ {
1748
+ newPassword,
1749
+ confirmPassword,
1750
+ onNewPasswordChange: setNewPassword,
1751
+ onConfirmPasswordChange: setConfirmPassword,
1752
+ onSubmit: handleSubmit,
1753
+ error,
1754
+ loading,
1755
+ emailAuthConfig: emailConfig,
1756
+ backToSignInUrl,
1757
+ ...uiProps
1758
+ }
1759
+ );
1760
+ }
1761
+ function ForgotPassword({
1762
+ backToSignInUrl = "/sign-in",
1763
+ onSuccess,
1764
+ onError,
1765
+ ...uiProps
1766
+ }) {
1767
+ const { sendPasswordResetCode, baseUrl } = useInsforge();
1768
+ const { emailConfig } = usePublicAuthConfig();
1769
+ const [insforge] = useState(() => createClient({ baseUrl }));
1770
+ const [step, setStep] = useState("email");
1771
+ const [email, setEmail] = useState("");
1772
+ const [verificationCode, setVerificationCode] = useState("");
1773
+ const [resetToken, setResetToken] = useState("");
1774
+ const [error, setError] = useState("");
1775
+ const [loading, setLoading] = useState(false);
1776
+ const [success, setSuccess] = useState(false);
1777
+ const [resendDisabled, setResendDisabled] = useState(true);
1778
+ const [resendCountdown, setResendCountdown] = useState(60);
1779
+ const [isSendingCode, setIsSendingCode] = useState(false);
1780
+ const [isVerifyingCode, setIsVerifyingCode] = useState(false);
1781
+ useEffect(() => {
1782
+ if (resendCountdown > 0 && step === "code") {
1783
+ const timer = setInterval(() => {
1784
+ setResendCountdown((prev) => {
1785
+ if (prev <= 1) {
1786
+ setResendDisabled(false);
1787
+ return 0;
1788
+ }
1789
+ return prev - 1;
1790
+ });
1791
+ }, 1e3);
1792
+ return () => clearInterval(timer);
1793
+ }
1794
+ }, [resendCountdown, step]);
1795
+ async function handleEmailSubmit(e) {
1796
+ e.preventDefault();
1797
+ setLoading(true);
1798
+ setError("");
1799
+ const emailValidation = emailSchema.safeParse(email);
1800
+ if (!emailValidation.success) {
1801
+ const firstError = emailValidation.error.issues[0];
1802
+ setError(firstError.message);
1803
+ setLoading(false);
1804
+ return;
1805
+ }
1806
+ try {
1807
+ const result = await sendPasswordResetCode(emailValidation.data);
1808
+ if (result?.success) {
1809
+ if (emailConfig?.resetPasswordMethod === "link") {
1810
+ setSuccess(true);
1811
+ if (onSuccess) {
1812
+ onSuccess();
1813
+ }
1814
+ } else {
1815
+ setStep("code");
1816
+ setResendDisabled(true);
1817
+ setResendCountdown(60);
1818
+ }
1819
+ } else {
1820
+ const errorMessage = result?.message || "Failed to send reset code";
1821
+ setError(errorMessage);
1822
+ if (onError) {
1823
+ onError(new Error(errorMessage));
1824
+ }
1825
+ }
1826
+ } catch (err) {
1827
+ const errorMessage = err.message || "Failed to send reset code";
1828
+ setError(errorMessage);
1829
+ if (onError) {
1830
+ onError(new Error(errorMessage));
1831
+ }
1832
+ } finally {
1833
+ setLoading(false);
1834
+ }
1835
+ }
1836
+ async function handleVerifyCode(code) {
1837
+ setIsVerifyingCode(true);
1838
+ setError("");
1839
+ setVerificationCode(code);
1840
+ try {
1841
+ const result = await insforge.auth.verifyResetPasswordCode({ email, code });
1842
+ if (result.error) {
1843
+ throw new Error(result.error.message || "Failed to verify code");
1844
+ }
1845
+ if (result.data) {
1846
+ setResetToken(result.data.resetToken);
1847
+ setStep("password");
1848
+ }
1849
+ } catch (err) {
1850
+ setError(err.message || "Invalid verification code");
1851
+ setVerificationCode("");
1852
+ } finally {
1853
+ setIsVerifyingCode(false);
1854
+ }
1855
+ }
1856
+ const handleResendCode = useCallback(async () => {
1857
+ setResendDisabled(true);
1858
+ setResendCountdown(60);
1859
+ setIsSendingCode(true);
1860
+ setError("");
1861
+ try {
1862
+ await sendPasswordResetCode(email);
1863
+ } catch (err) {
1864
+ setError(err.message || "Failed to resend code");
1865
+ setResendDisabled(false);
1866
+ setResendCountdown(0);
1867
+ } finally {
1868
+ setIsSendingCode(false);
1869
+ }
1870
+ }, [email, sendPasswordResetCode]);
1871
+ function handlePasswordResetSuccess() {
1872
+ if (onSuccess) {
1873
+ onSuccess();
1874
+ }
1875
+ }
1876
+ if (!emailConfig) {
1877
+ return null;
1878
+ }
1879
+ if (step === "email") {
1880
+ return /* @__PURE__ */ jsx(
1881
+ ForgotPasswordForm,
1882
+ {
1883
+ email,
1884
+ onEmailChange: setEmail,
1885
+ onSubmit: handleEmailSubmit,
1886
+ error,
1887
+ loading,
1888
+ success,
1889
+ backToSignInUrl,
1890
+ ...uiProps
1891
+ }
1892
+ );
1893
+ }
1894
+ if (step === "code") {
1895
+ return /* @__PURE__ */ jsxs(
1896
+ AuthContainer,
1897
+ {
1898
+ appearance: {
1899
+ containerClassName: uiProps.appearance?.container,
1900
+ cardClassName: uiProps.appearance?.card
1901
+ },
1902
+ children: [
1903
+ /* @__PURE__ */ jsx(
1904
+ AuthHeader,
1905
+ {
1906
+ title: "Enter Reset Code",
1907
+ 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.`,
1908
+ appearance: {
1909
+ containerClassName: uiProps.appearance?.header?.container,
1910
+ titleClassName: uiProps.appearance?.header?.title,
1911
+ subtitleClassName: uiProps.appearance?.header?.subtitle
1912
+ }
1913
+ }
1914
+ ),
1915
+ /* @__PURE__ */ jsx(AuthErrorBanner, { error, className: uiProps.appearance?.errorBanner }),
1916
+ /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col gap-6 items-center", children: [
1917
+ /* @__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: [
1918
+ /* @__PURE__ */ jsx(
1919
+ AuthVerificationCodeInput,
1920
+ {
1921
+ value: verificationCode,
1922
+ onChange: setVerificationCode,
1923
+ email,
1924
+ disabled: isVerifyingCode,
1925
+ onComplete: (code) => {
1926
+ void handleVerifyCode(code);
1927
+ }
1928
+ }
1929
+ ),
1930
+ isVerifyingCode && /* @__PURE__ */ jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 text-center", children: "Verifying..." })
1931
+ ] }) }),
1932
+ /* @__PURE__ */ jsxs("div", { className: "w-full text-sm text-center text-neutral-600 dark:text-neutral-400", children: [
1933
+ "Didn't receive the email?",
1934
+ " ",
1935
+ /* @__PURE__ */ jsx(
1936
+ "button",
1937
+ {
1938
+ onClick: () => {
1939
+ void handleResendCode();
1940
+ },
1941
+ disabled: resendDisabled || isSendingCode,
1942
+ className: "text-black dark:text-white font-medium transition-colors disabled:cursor-not-allowed cursor-pointer hover:underline disabled:no-underline disabled:opacity-50",
1943
+ children: isSendingCode ? "Sending..." : resendDisabled ? `Retry in (${resendCountdown}s)` : "Click to resend"
1944
+ }
1945
+ )
1946
+ ] })
1947
+ ] }),
1948
+ /* @__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" }) })
1949
+ ]
1950
+ }
1951
+ );
1952
+ }
1953
+ return /* @__PURE__ */ jsx(
1954
+ ResetPassword,
1955
+ {
1956
+ token: resetToken,
1957
+ backToSignInUrl,
1958
+ onSuccess: handlePasswordResetSuccess,
1959
+ onError,
1960
+ appearance: uiProps.appearance
1961
+ }
1962
+ );
1963
+ }
1964
+ function VerifyEmailStatus({
1965
+ status,
1966
+ error,
1967
+ appearance = {},
1968
+ verifyingTitle = "Verifying your email...",
1969
+ successTitle = "Email Verified!",
1970
+ successMessage = "Your email has been verified successfully. You can close this page and return to your app.",
1971
+ errorTitle = "Verification Failed"
1972
+ }) {
1973
+ if (status === "verifying") {
1974
+ return /* @__PURE__ */ jsx(AuthContainer, { appearance, children: /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col items-center justify-center gap-6", children: [
1975
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold text-black dark:text-white", children: verifyingTitle }),
1976
+ /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-black dark:border-white" })
1977
+ ] }) });
1978
+ }
1979
+ if (status === "error") {
1980
+ 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: [
1981
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold text-black dark:text-white", children: errorTitle }),
1982
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed", children: [
1983
+ error || "The verification link is invalid or has expired.",
1984
+ " Please try again or contact support if the problem persists. You can close this page and return to your app."
1985
+ ] })
1986
+ ] }) }) });
1987
+ }
1988
+ 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: [
1989
+ /* @__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" }) }) }),
1990
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold text-black dark:text-white text-center", children: successTitle }),
1991
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 text-center", children: successMessage })
1992
+ ] }) }) });
1993
+ }
1994
+ function VerifyEmail({
1995
+ token,
1996
+ onSuccess,
1997
+ onError,
1998
+ ...uiProps
1999
+ }) {
2000
+ const { verifyEmail } = useInsforge();
2001
+ const [status, setStatus] = useState("verifying");
2002
+ const [error, setError] = useState("");
2003
+ useEffect(() => {
2004
+ const verifyEmailFn = async () => {
2005
+ if (!token) {
2006
+ const errorMessage = "Invalid verification link. Missing required token.";
2007
+ setError(errorMessage);
2008
+ setStatus("error");
2009
+ if (onError) {
2010
+ onError(new Error(errorMessage));
2011
+ }
2012
+ return;
2013
+ }
2014
+ try {
2015
+ const result = await verifyEmail(token);
2016
+ if (!result?.accessToken) {
2017
+ const errorMessage = result ? "Verification succeeded but no access token received" : "Email verification failed";
2018
+ setError(errorMessage);
2019
+ setStatus("error");
2020
+ if (onError) {
2021
+ onError(new Error(errorMessage));
2022
+ }
2023
+ return;
2024
+ }
2025
+ setStatus("success");
2026
+ if (onSuccess) {
2027
+ onSuccess({
2028
+ accessToken: result.accessToken,
2029
+ user: result.user
2030
+ });
2031
+ }
2032
+ } catch (err) {
2033
+ const errorMessage = err.message || "Email verification failed";
2034
+ setError(errorMessage);
2035
+ setStatus("error");
2036
+ if (onError) {
2037
+ onError(new Error(errorMessage));
2038
+ }
2039
+ }
2040
+ };
2041
+ void verifyEmailFn();
2042
+ }, [token, verifyEmail, onSuccess, onError]);
2043
+ return /* @__PURE__ */ jsx(VerifyEmailStatus, { status, error, ...uiProps });
2044
+ }
2045
+ function UserButton({
2046
+ afterSignOutUrl = "/",
2047
+ mode = "detailed",
2048
+ appearance = {}
2049
+ }) {
2050
+ const { user, signOut } = useInsforge();
2051
+ const [isOpen, setIsOpen] = useState(false);
2052
+ const [imageError, setImageError] = useState(false);
2053
+ const dropdownRef = useRef(null);
2054
+ useEffect(() => {
2055
+ setImageError(false);
2056
+ const avatarUrl = user?.avatarUrl;
2057
+ if (!avatarUrl) return;
2058
+ const checkImageUrl = async () => {
2059
+ try {
2060
+ const response = await fetch(avatarUrl, {
2061
+ method: "HEAD",
2062
+ cache: "no-cache"
2063
+ });
2064
+ if (!response.ok) {
2065
+ setImageError(true);
2066
+ }
2067
+ } catch (error) {
2068
+ setImageError(true);
2069
+ }
2070
+ };
2071
+ checkImageUrl();
2072
+ }, [user?.avatarUrl]);
2073
+ useEffect(() => {
2074
+ function handleClickOutside(event) {
2075
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
2076
+ setIsOpen(false);
2077
+ }
2078
+ }
2079
+ if (isOpen) {
2080
+ document.addEventListener("mousedown", handleClickOutside);
2081
+ }
2082
+ return () => {
2083
+ document.removeEventListener("mousedown", handleClickOutside);
2084
+ };
2085
+ }, [isOpen]);
2086
+ async function handleSignOut() {
2087
+ await signOut();
2088
+ setIsOpen(false);
2089
+ window.location.href = afterSignOutUrl;
2090
+ }
2091
+ if (!user) return null;
2092
+ const initials = user.name ? user.name.charAt(0).toUpperCase() : user.email.split("@")[0].slice(0, 2).toUpperCase();
2093
+ return /* @__PURE__ */ jsxs(
2094
+ "div",
2095
+ {
2096
+ className: cn("relative inline-block", appearance.containerClassName),
2097
+ ref: dropdownRef,
2098
+ children: [
2099
+ /* @__PURE__ */ jsxs(
2100
+ "button",
2101
+ {
2102
+ className: cn(
2103
+ "p-1 bg-transparent border-0 rounded-full cursor-pointer transition-all duration-200",
2104
+ "flex items-center justify-center gap-2",
2105
+ "hover:bg-black/5",
2106
+ mode === "detailed" && "rounded-lg p-2",
2107
+ appearance.buttonClassName
2108
+ ),
2109
+ onClick: () => setIsOpen(!isOpen),
2110
+ "aria-expanded": isOpen,
2111
+ "aria-haspopup": "true",
2112
+ children: [
2113
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center w-10 h-10 bg-blue-500 rounded-full", children: user.avatarUrl && !imageError ? /* @__PURE__ */ jsx(
2114
+ "img",
2115
+ {
2116
+ src: user.avatarUrl,
2117
+ alt: user.email,
2118
+ onError: () => setImageError(true),
2119
+ className: "rounded-full object-cover w-full h-full"
2120
+ }
2121
+ ) : /* @__PURE__ */ jsx("span", { className: "text-white font-semibold text-sm", children: initials }) }),
2122
+ mode === "detailed" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-0.5", children: [
2123
+ user.name && /* @__PURE__ */ jsx(
2124
+ "div",
2125
+ {
2126
+ className: cn(
2127
+ "text-sm font-semibold text-gray-900 leading-5 text-left",
2128
+ appearance.nameClassName
2129
+ ),
2130
+ children: user.name
2131
+ }
2132
+ ),
2133
+ /* @__PURE__ */ jsx(
2134
+ "div",
2135
+ {
2136
+ className: cn(
2137
+ "text-xs text-gray-500 leading-4 text-left",
2138
+ appearance.emailClassName
2139
+ ),
2140
+ children: user.email
2141
+ }
2142
+ )
2143
+ ] })
2144
+ ]
2145
+ }
2146
+ ),
2147
+ isOpen && /* @__PURE__ */ jsx(
2148
+ "div",
2149
+ {
2150
+ className: cn(
2151
+ "absolute top-full right-0 mt-2 min-w-40",
2152
+ "bg-white border border-gray-200 rounded-lg",
2153
+ "shadow-lg z-50 overflow-hidden p-1",
2154
+ appearance.dropdownClassName
2155
+ ),
2156
+ children: /* @__PURE__ */ jsxs(
2157
+ "button",
2158
+ {
2159
+ onClick: handleSignOut,
2160
+ className: "flex items-center justify-start gap-2 w-full px-3 py-2 text-sm font-normal text-red-600 bg-transparent border-0 rounded-md cursor-pointer transition-colors hover:bg-red-50 text-left",
2161
+ children: [
2162
+ /* @__PURE__ */ jsx(LogOut, { className: "w-5 h-5" }),
2163
+ "Sign out"
2164
+ ]
2165
+ }
2166
+ )
2167
+ }
2168
+ )
2169
+ ]
2170
+ }
2171
+ );
2172
+ }
2173
+ function Protect({
2174
+ children,
2175
+ fallback,
2176
+ redirectTo = "/sign-in",
2177
+ condition,
2178
+ onRedirect
2179
+ }) {
2180
+ const { isSignedIn, isLoaded, user } = useInsforge();
2181
+ useEffect(() => {
2182
+ if (isLoaded && !isSignedIn) {
2183
+ if (onRedirect) {
2184
+ onRedirect(redirectTo);
2185
+ } else {
2186
+ window.location.href = redirectTo;
2187
+ }
2188
+ } else if (isLoaded && isSignedIn && condition && user) {
2189
+ if (!condition(user)) {
2190
+ if (onRedirect) {
2191
+ onRedirect(redirectTo);
2192
+ } else {
2193
+ window.location.href = redirectTo;
2194
+ }
2195
+ }
2196
+ }
2197
+ }, [isLoaded, isSignedIn, redirectTo, condition, user, onRedirect]);
2198
+ if (!isLoaded) {
2199
+ return fallback || /* @__PURE__ */ jsx("div", { className: "insforge-loading", children: "Loading..." });
2200
+ }
2201
+ if (!isSignedIn) {
2202
+ return fallback || null;
2203
+ }
2204
+ if (condition && user && !condition(user)) {
2205
+ return fallback || null;
2206
+ }
2207
+ return /* @__PURE__ */ jsx(Fragment, { children });
2208
+ }
2209
+ function SignedIn({ children }) {
2210
+ const { isSignedIn, isLoaded } = useInsforge();
2211
+ if (!isLoaded) return null;
2212
+ if (!isSignedIn) return null;
2213
+ return /* @__PURE__ */ jsx(Fragment, { children });
2214
+ }
2215
+ function SignedOut({ children }) {
2216
+ const { isSignedIn, isLoaded } = useInsforge();
2217
+ if (!isLoaded) return null;
2218
+ if (isSignedIn) return null;
2219
+ return /* @__PURE__ */ jsx(Fragment, { children });
2220
+ }
2221
+ function InsforgeCallback({
2222
+ redirectTo = "/",
2223
+ onSuccess,
2224
+ onError,
2225
+ loadingComponent,
2226
+ onRedirect
2227
+ }) {
2228
+ const isProcessingRef = useRef(false);
2229
+ const { isLoaded, isSignedIn } = useInsforge();
2230
+ useEffect(() => {
2231
+ if (!isLoaded) return;
2232
+ if (isProcessingRef.current) return;
2233
+ isProcessingRef.current = true;
2234
+ const processCallback = async () => {
2235
+ const searchParams = new URLSearchParams(window.location.search);
2236
+ const error = searchParams.get("error");
2237
+ if (error) {
2238
+ if (onError) {
2239
+ onError(error);
2240
+ } else {
2241
+ const errorUrl = "/?error=" + encodeURIComponent(error);
2242
+ if (onRedirect) {
2243
+ onRedirect(errorUrl);
2244
+ } else {
2245
+ window.location.href = errorUrl;
2246
+ }
2247
+ }
2248
+ return;
2249
+ }
2250
+ if (!isSignedIn) {
2251
+ const errorMsg = "authentication_failed";
2252
+ if (onError) {
2253
+ onError(errorMsg);
2254
+ } else {
2255
+ const errorUrl = "/?error=" + encodeURIComponent(errorMsg);
2256
+ if (onRedirect) {
2257
+ onRedirect(errorUrl);
2258
+ } else {
2259
+ window.location.href = errorUrl;
2260
+ }
2261
+ }
2262
+ return;
2263
+ }
2264
+ window.history.replaceState({}, "", window.location.pathname);
2265
+ if (onSuccess) {
2266
+ onSuccess();
2267
+ }
2268
+ if (onRedirect) {
2269
+ onRedirect(redirectTo);
2270
+ } else {
2271
+ window.location.href = redirectTo;
2272
+ }
2273
+ };
2274
+ processCallback();
2275
+ }, [isLoaded, isSignedIn, redirectTo, onSuccess, onError, onRedirect]);
2276
+ const defaultLoading = /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2277
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold mb-4", children: "Completing authentication..." }),
2278
+ /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto" })
2279
+ ] }) });
2280
+ return loadingComponent || defaultLoading;
1727
2281
  }
1728
2282
 
1729
- 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 };
2283
+ 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 };
1730
2284
  //# sourceMappingURL=components.mjs.map
1731
2285
  //# sourceMappingURL=components.mjs.map