@kendevelops/auth-flow-kit 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -34,7 +34,9 @@ var import_react = require("react");
34
34
 
35
35
  // src/http.ts
36
36
  function makeURL(baseURL, path) {
37
- return `${baseURL.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
37
+ const base = baseURL.replace(/\/$/, "");
38
+ const suffix = path.startsWith("/") ? path : `/${path}`;
39
+ return `${base}${suffix}`;
38
40
  }
39
41
  function getStoredAccessToken() {
40
42
  try {
@@ -45,8 +47,11 @@ function getStoredAccessToken() {
45
47
  }
46
48
  function setStoredAccessToken(token) {
47
49
  try {
48
- if (token) localStorage.setItem("afk_access_token", token);
49
- else localStorage.removeItem("afk_access_token");
50
+ if (token) {
51
+ localStorage.setItem("afk_access_token", token);
52
+ return;
53
+ }
54
+ localStorage.removeItem("afk_access_token");
50
55
  } catch {
51
56
  }
52
57
  }
@@ -55,20 +60,27 @@ async function httpJSON(url, opts = {}, withAuth = false) {
55
60
  "Content-Type": "application/json"
56
61
  };
57
62
  if (withAuth) {
58
- const tok = getStoredAccessToken();
59
- if (tok) headers["Authorization"] = `Bearer ${tok}`;
63
+ const storedToken = getStoredAccessToken();
64
+ if (storedToken) {
65
+ headers["Authorization"] = `Bearer ${storedToken}`;
66
+ }
60
67
  }
61
68
  const res = await fetch(url, {
62
69
  ...opts,
63
- headers: { ...headers, ...opts.headers || {} }
70
+ headers: {
71
+ ...headers,
72
+ ...opts.headers || {}
73
+ }
64
74
  });
65
75
  if (!res.ok) {
66
76
  let message = `Request failed (${res.status})`;
67
77
  const contentType = res.headers.get("content-type") || "";
68
78
  if (contentType.includes("application/json")) {
69
79
  try {
70
- const data = await res.json();
71
- if (data?.message) message = data.message;
80
+ const body = await res.json();
81
+ if (body?.message) {
82
+ message = body.message;
83
+ }
72
84
  } catch {
73
85
  }
74
86
  }
@@ -188,18 +200,29 @@ function PasswordResetScreen() {
188
200
  const [email, setEmail] = (0, import_react3.useState)("");
189
201
  const [sent, setSent] = (0, import_react3.useState)(false);
190
202
  const [error, setError] = (0, import_react3.useState)(null);
203
+ const isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
191
204
  const requestReset = async (e) => {
192
205
  e.preventDefault();
193
206
  setError(null);
207
+ const trimmedEmail = email.trim();
208
+ if (!trimmedEmail) {
209
+ setError("Email is required.");
210
+ return;
211
+ }
212
+ if (!isValidEmail(trimmedEmail)) {
213
+ setError("Enter a valid email address.");
214
+ return;
215
+ }
194
216
  try {
195
217
  const url = makeURL(config.baseURL, config.endpoints.forgot);
196
218
  await httpJSON(url, {
197
219
  method: "POST",
198
- body: JSON.stringify({ email })
220
+ body: JSON.stringify({ email: trimmedEmail })
199
221
  });
200
222
  setSent(true);
201
223
  } catch (err) {
202
- setError(err?.message || "Failed to request reset");
224
+ const message = err instanceof Error ? err.message : "Failed to request reset";
225
+ setError(message);
203
226
  }
204
227
  };
205
228
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
@@ -239,6 +262,7 @@ function PasswordResetScreen() {
239
262
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
240
263
  "label",
241
264
  {
265
+ htmlFor: "afk-reset-email",
242
266
  style: {
243
267
  position: "absolute",
244
268
  top: sent ? "-18px" : "-10px",
@@ -255,6 +279,7 @@ function PasswordResetScreen() {
255
279
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
256
280
  "input",
257
281
  {
282
+ id: "afk-reset-email",
258
283
  value: email,
259
284
  onChange: (e) => setEmail(e.target.value),
260
285
  type: "email",
@@ -317,6 +342,8 @@ function PasswordResetScreen() {
317
342
  error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
318
343
  "p",
319
344
  {
345
+ role: "alert",
346
+ "aria-live": "polite",
320
347
  style: {
321
348
  marginTop: 20,
322
349
  color: "crimson",
@@ -367,15 +394,26 @@ function LoginScreen() {
367
394
  setError(null);
368
395
  const trimmedEmail = email.trim();
369
396
  const trimmedPassword = password.trim();
370
- if (!trimmedEmail || !trimmedPassword) {
371
- setError("Email and password cannot be empty.");
397
+ if (!trimmedEmail) {
398
+ setError("Email is required.");
399
+ setSubmitting(false);
400
+ return;
401
+ }
402
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmedEmail)) {
403
+ setError("Enter a valid email address.");
404
+ setSubmitting(false);
405
+ return;
406
+ }
407
+ if (!trimmedPassword) {
408
+ setError("Password is required.");
372
409
  setSubmitting(false);
373
410
  return;
374
411
  }
375
412
  try {
376
413
  await login(trimmedEmail, trimmedPassword);
377
414
  } catch (err) {
378
- setError(err?.message || "Login failed");
415
+ const message = err instanceof Error ? err.message : "Login failed";
416
+ setError(message);
379
417
  } finally {
380
418
  setSubmitting(false);
381
419
  }
@@ -416,6 +454,7 @@ function LoginScreen() {
416
454
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
417
455
  "label",
418
456
  {
457
+ htmlFor: "afk-login-email",
419
458
  style: {
420
459
  position: "absolute",
421
460
  top: "-10px",
@@ -432,6 +471,7 @@ function LoginScreen() {
432
471
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
433
472
  "input",
434
473
  {
474
+ id: "afk-login-email",
435
475
  value: email,
436
476
  onChange: (e) => setEmail(e.target.value),
437
477
  type: "email",
@@ -461,6 +501,7 @@ function LoginScreen() {
461
501
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
462
502
  "label",
463
503
  {
504
+ htmlFor: "afk-login-password",
464
505
  style: {
465
506
  position: "absolute",
466
507
  top: "-10px",
@@ -477,6 +518,7 @@ function LoginScreen() {
477
518
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
478
519
  "input",
479
520
  {
521
+ id: "afk-login-password",
480
522
  value: password,
481
523
  onChange: (e) => setPassword(e.target.value),
482
524
  type: "password",
@@ -503,17 +545,28 @@ function LoginScreen() {
503
545
  )
504
546
  ] }),
505
547
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
506
- "p",
548
+ "div",
507
549
  {
508
- onClick: () => setShowReset(true),
509
550
  style: {
510
- textAlign: "right",
511
- fontSize: 13,
512
- color: "#4b4bff",
513
- cursor: "pointer",
514
- marginBottom: 24
551
+ textAlign: "right"
515
552
  },
516
- children: "Forgot password?"
553
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
554
+ "button",
555
+ {
556
+ onClick: () => setShowReset(true),
557
+ style: {
558
+ textAlign: "right",
559
+ fontSize: 13,
560
+ color: "#4b4bff",
561
+ cursor: "pointer",
562
+ marginBottom: 24,
563
+ width: "fit-content",
564
+ border: "none",
565
+ background: "none"
566
+ },
567
+ children: "Forgot password?"
568
+ }
569
+ )
517
570
  }
518
571
  ),
519
572
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -541,6 +594,8 @@ function LoginScreen() {
541
594
  error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
542
595
  "p",
543
596
  {
597
+ role: "alert",
598
+ "aria-live": "polite",
544
599
  style: {
545
600
  marginTop: 18,
546
601
  color: "crimson",
@@ -564,8 +619,21 @@ function SignupScreen() {
564
619
  const [name, setName] = (0, import_react5.useState)("");
565
620
  const [email, setEmail] = (0, import_react5.useState)("");
566
621
  const [password, setPassword] = (0, import_react5.useState)("");
622
+ const [confirmPassword, setConfirmPassword] = (0, import_react5.useState)("");
567
623
  const [submitting, setSubmitting] = (0, import_react5.useState)(false);
568
624
  const [error, setError] = (0, import_react5.useState)(null);
625
+ const isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
626
+ const getPasswordStrength = (value) => {
627
+ let score = 0;
628
+ if (value.length >= 8) score++;
629
+ if (/[A-Z]/.test(value)) score++;
630
+ if (/[0-9]/.test(value)) score++;
631
+ if (/[^A-Za-z0-9]/.test(value)) score++;
632
+ if (!value) return { label: "", color: "" };
633
+ if (score <= 1) return { label: "Weak", color: "crimson" };
634
+ if (score === 2) return { label: "Medium", color: "#f39c12" };
635
+ return { label: "Strong", color: "#2ecc71" };
636
+ };
569
637
  const onSubmit = async (e) => {
570
638
  e.preventDefault();
571
639
  setSubmitting(true);
@@ -573,15 +641,47 @@ function SignupScreen() {
573
641
  const trimmedEmail = email.trim();
574
642
  const trimmedPassword = password.trim();
575
643
  const trimmedName = name.trim();
576
- if (!trimmedEmail || !trimmedPassword || !trimmedName) {
577
- setError("Email and password cannot be empty.");
644
+ const trimmedConfirm = confirmPassword.trim();
645
+ if (!trimmedName) {
646
+ setError("Name is required.");
647
+ setSubmitting(false);
648
+ return;
649
+ }
650
+ if (!trimmedEmail) {
651
+ setError("Email is required.");
652
+ setSubmitting(false);
653
+ return;
654
+ }
655
+ if (!isValidEmail(trimmedEmail)) {
656
+ setError("Enter a valid email address.");
657
+ setSubmitting(false);
658
+ return;
659
+ }
660
+ if (!trimmedPassword) {
661
+ setError("Password is required.");
662
+ setSubmitting(false);
663
+ return;
664
+ }
665
+ if (trimmedPassword.length < 8) {
666
+ setError("Password must be at least 8 characters long.");
667
+ setSubmitting(false);
668
+ return;
669
+ }
670
+ if (!trimmedConfirm) {
671
+ setError("Please confirm your password.");
672
+ setSubmitting(false);
673
+ return;
674
+ }
675
+ if (trimmedPassword !== trimmedConfirm) {
676
+ setError("Passwords do not match.");
578
677
  setSubmitting(false);
579
678
  return;
580
679
  }
581
680
  try {
582
- await signup({ name, email, password });
681
+ await signup({ name: trimmedName, email: trimmedEmail, password: trimmedPassword });
583
682
  } catch (err) {
584
- setError(err?.message || "Signup failed");
683
+ const message = err instanceof Error ? err.message : "Signup failed";
684
+ setError(message);
585
685
  } finally {
586
686
  setSubmitting(false);
587
687
  }
@@ -623,6 +723,7 @@ function SignupScreen() {
623
723
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
624
724
  "label",
625
725
  {
726
+ htmlFor: "afk-signup-name",
626
727
  style: {
627
728
  position: "absolute",
628
729
  top: "-10px",
@@ -639,6 +740,7 @@ function SignupScreen() {
639
740
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
640
741
  "input",
641
742
  {
743
+ id: "afk-signup-name",
642
744
  value: name,
643
745
  onChange: (e) => setName(e.target.value),
644
746
  placeholder: "Your name",
@@ -667,6 +769,7 @@ function SignupScreen() {
667
769
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
668
770
  "label",
669
771
  {
772
+ htmlFor: "afk-signup-email",
670
773
  style: {
671
774
  position: "absolute",
672
775
  top: "-10px",
@@ -683,6 +786,7 @@ function SignupScreen() {
683
786
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
684
787
  "input",
685
788
  {
789
+ id: "afk-signup-email",
686
790
  value: email,
687
791
  onChange: (e) => setEmail(e.target.value),
688
792
  type: "email",
@@ -712,6 +816,7 @@ function SignupScreen() {
712
816
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
713
817
  "label",
714
818
  {
819
+ htmlFor: "afk-signup-password",
715
820
  style: {
716
821
  position: "absolute",
717
822
  top: "-10px",
@@ -728,6 +833,7 @@ function SignupScreen() {
728
833
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
729
834
  "input",
730
835
  {
836
+ id: "afk-signup-password",
731
837
  value: password,
732
838
  onChange: (e) => setPassword(e.target.value),
733
839
  type: "password",
@@ -753,6 +859,95 @@ function SignupScreen() {
753
859
  }
754
860
  )
755
861
  ] }),
862
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { position: "relative", marginBottom: 18 }, children: [
863
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
864
+ "label",
865
+ {
866
+ htmlFor: "afk-signup-confirm-password",
867
+ style: {
868
+ position: "absolute",
869
+ top: "-10px",
870
+ left: "14px",
871
+ background: "rgba(255,255,255,0.85)",
872
+ padding: "0 6px",
873
+ fontSize: 13,
874
+ color: "#444",
875
+ borderRadius: 6
876
+ },
877
+ children: "Confirm password"
878
+ }
879
+ ),
880
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
881
+ "input",
882
+ {
883
+ id: "afk-signup-confirm-password",
884
+ value: confirmPassword,
885
+ onChange: (e) => setConfirmPassword(e.target.value),
886
+ type: "password",
887
+ placeholder: "Repeat your password",
888
+ style: {
889
+ width: "80%",
890
+ padding: "14px 16px",
891
+ borderRadius: 12,
892
+ border: "1px solid #d2d2d2",
893
+ fontSize: 15,
894
+ outline: "none",
895
+ transition: "0.25s",
896
+ background: "rgba(255,255,255,0.85)"
897
+ },
898
+ onFocus: (e) => {
899
+ e.currentTarget.style.border = "1px solid #4b4bff";
900
+ e.currentTarget.style.boxShadow = "0 0 0 3px rgba(75,75,255,0.25)";
901
+ },
902
+ onBlur: (e) => {
903
+ e.currentTarget.style.border = "1px solid #d2d2d2";
904
+ e.currentTarget.style.boxShadow = "0 0 0 transparent";
905
+ }
906
+ }
907
+ )
908
+ ] }),
909
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
910
+ "div",
911
+ {
912
+ style: {
913
+ marginBottom: 20,
914
+ fontSize: 12,
915
+ color: "#555"
916
+ },
917
+ children: [
918
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: { marginBottom: 6 }, children: "Use at least 8 characters, including letters, numbers, and symbols." }),
919
+ password && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
920
+ "p",
921
+ {
922
+ style: {
923
+ fontWeight: 600,
924
+ color: getPasswordStrength(password).color
925
+ },
926
+ children: [
927
+ "Password strength: ",
928
+ getPasswordStrength(password).label
929
+ ]
930
+ }
931
+ )
932
+ ]
933
+ }
934
+ ),
935
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
936
+ "p",
937
+ {
938
+ style: {
939
+ textAlign: "center",
940
+ fontSize: 13,
941
+ color: "#4b4bff",
942
+ cursor: "pointer",
943
+ marginBottom: 24
944
+ },
945
+ onClick: () => {
946
+ window.dispatchEvent(new CustomEvent("auth-flow-kit:navigate-to-login"));
947
+ },
948
+ children: "Already have an account? Sign in"
949
+ }
950
+ ),
756
951
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
757
952
  "button",
758
953
  {
@@ -778,6 +973,8 @@ function SignupScreen() {
778
973
  error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
779
974
  "p",
780
975
  {
976
+ role: "alert",
977
+ "aria-live": "polite",
781
978
  style: {
782
979
  marginTop: 18,
783
980
  color: "crimson",