@kendevelops/auth-flow-kit 1.2.2 → 1.2.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 normalizedBase = baseURL.replace(/\/$/, "");
38
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
39
+ return `${normalizedBase}${normalizedPath}`;
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 !== null) {
51
+ localStorage.setItem("afk_access_token", token);
52
+ return;
53
+ }
54
+ localStorage.removeItem("afk_access_token");
50
55
  } catch {
51
56
  }
52
57
  }
@@ -54,46 +59,53 @@ async function httpJSON(url, opts = {}, withAuth = false) {
54
59
  const headers = {
55
60
  "Content-Type": "application/json"
56
61
  };
57
- if (withAuth) {
58
- const tok = getStoredAccessToken();
59
- if (tok) headers["Authorization"] = `Bearer ${tok}`;
62
+ if (withAuth === true) {
63
+ const token = getStoredAccessToken();
64
+ if (token) {
65
+ headers["Authorization"] = `Bearer ${token}`;
66
+ }
60
67
  }
61
- const res = await fetch(url, {
68
+ const response = await fetch(url, {
62
69
  ...opts,
63
- headers: { ...headers, ...opts.headers || {} }
70
+ headers: {
71
+ ...headers,
72
+ ...opts.headers || {}
73
+ }
64
74
  });
65
- if (!res.ok) {
66
- let message = `Request failed (${res.status})`;
67
- const contentType = res.headers.get("content-type") || "";
68
- if (contentType.includes("application/json")) {
69
- try {
70
- const data = await res.json();
71
- if (data?.message) message = data.message;
72
- } catch {
75
+ if (response.ok) {
76
+ return response.json();
77
+ }
78
+ let message = `Request failed (${response.status})`;
79
+ const contentType = response.headers.get("content-type") || "";
80
+ if (contentType.includes("application/json")) {
81
+ try {
82
+ const data = await response.json();
83
+ if (data?.message) {
84
+ message = data.message;
73
85
  }
86
+ } catch {
74
87
  }
75
- if (contentType.includes("text/html")) {
76
- if (res.status === 404 && url.includes("forgot")) {
77
- message = "The forgot password endpoint you added in config.endpoints.forgot does not exist in your server. Please check and update your config.endpoints.forgot";
78
- } else {
79
- message = "Unexpected server error";
80
- }
88
+ }
89
+ if (contentType.includes("text/html")) {
90
+ if (response.status === 404 && url.includes("forgot")) {
91
+ message = "The forgot password endpoint you added in config.endpoints.forgot does not exist in your server. Please check and update your config.endpoints.forgot";
92
+ } else {
93
+ message = "Unexpected server error";
81
94
  }
82
- if (res.status === 404 && url.includes("forgot")) {
83
- console.error(
84
- `[auth-flow-kit] Password reset endpoint not found.
95
+ }
96
+ if (response.status === 404 && url.includes("forgot")) {
97
+ console.error(
98
+ `[auth-flow-kit] Password reset endpoint not found.
85
99
 
86
- Expected a POST route matching:
87
- ${url}
100
+ Expected a POST route matching:
101
+ ${url}
88
102
 
89
- Fix this by either:
90
- - Adding the route on your backend, or
91
- - Updating config.endpoints.forgot`
92
- );
93
- }
94
- throw new Error(message);
103
+ Fix this by either:
104
+ - Adding the route on your backend, or
105
+ - Updating config.endpoints.forgot`
106
+ );
95
107
  }
96
- return res.json();
108
+ throw new Error(message);
97
109
  }
98
110
 
99
111
  // src/AuthContext.tsx
@@ -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",
@@ -541,6 +583,8 @@ function LoginScreen() {
541
583
  error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
542
584
  "p",
543
585
  {
586
+ role: "alert",
587
+ "aria-live": "polite",
544
588
  style: {
545
589
  marginTop: 18,
546
590
  color: "crimson",
@@ -564,8 +608,21 @@ function SignupScreen() {
564
608
  const [name, setName] = (0, import_react5.useState)("");
565
609
  const [email, setEmail] = (0, import_react5.useState)("");
566
610
  const [password, setPassword] = (0, import_react5.useState)("");
611
+ const [confirmPassword, setConfirmPassword] = (0, import_react5.useState)("");
567
612
  const [submitting, setSubmitting] = (0, import_react5.useState)(false);
568
613
  const [error, setError] = (0, import_react5.useState)(null);
614
+ const isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
615
+ const getPasswordStrength = (value) => {
616
+ let score = 0;
617
+ if (value.length >= 8) score++;
618
+ if (/[A-Z]/.test(value)) score++;
619
+ if (/[0-9]/.test(value)) score++;
620
+ if (/[^A-Za-z0-9]/.test(value)) score++;
621
+ if (!value) return { label: "", color: "" };
622
+ if (score <= 1) return { label: "Weak", color: "crimson" };
623
+ if (score === 2) return { label: "Medium", color: "#f39c12" };
624
+ return { label: "Strong", color: "#2ecc71" };
625
+ };
569
626
  const onSubmit = async (e) => {
570
627
  e.preventDefault();
571
628
  setSubmitting(true);
@@ -573,15 +630,47 @@ function SignupScreen() {
573
630
  const trimmedEmail = email.trim();
574
631
  const trimmedPassword = password.trim();
575
632
  const trimmedName = name.trim();
576
- if (!trimmedEmail || !trimmedPassword || !trimmedName) {
577
- setError("Email and password cannot be empty.");
633
+ const trimmedConfirm = confirmPassword.trim();
634
+ if (!trimmedName) {
635
+ setError("Name is required.");
636
+ setSubmitting(false);
637
+ return;
638
+ }
639
+ if (!trimmedEmail) {
640
+ setError("Email is required.");
641
+ setSubmitting(false);
642
+ return;
643
+ }
644
+ if (!isValidEmail(trimmedEmail)) {
645
+ setError("Enter a valid email address.");
646
+ setSubmitting(false);
647
+ return;
648
+ }
649
+ if (!trimmedPassword) {
650
+ setError("Password is required.");
651
+ setSubmitting(false);
652
+ return;
653
+ }
654
+ if (trimmedPassword.length < 8) {
655
+ setError("Password must be at least 8 characters long.");
656
+ setSubmitting(false);
657
+ return;
658
+ }
659
+ if (!trimmedConfirm) {
660
+ setError("Please confirm your password.");
661
+ setSubmitting(false);
662
+ return;
663
+ }
664
+ if (trimmedPassword !== trimmedConfirm) {
665
+ setError("Passwords do not match.");
578
666
  setSubmitting(false);
579
667
  return;
580
668
  }
581
669
  try {
582
- await signup({ name, email, password });
670
+ await signup({ name: trimmedName, email: trimmedEmail, password: trimmedPassword });
583
671
  } catch (err) {
584
- setError(err?.message || "Signup failed");
672
+ const message = err instanceof Error ? err.message : "Signup failed";
673
+ setError(message);
585
674
  } finally {
586
675
  setSubmitting(false);
587
676
  }
@@ -623,6 +712,7 @@ function SignupScreen() {
623
712
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
624
713
  "label",
625
714
  {
715
+ htmlFor: "afk-signup-name",
626
716
  style: {
627
717
  position: "absolute",
628
718
  top: "-10px",
@@ -639,6 +729,7 @@ function SignupScreen() {
639
729
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
640
730
  "input",
641
731
  {
732
+ id: "afk-signup-name",
642
733
  value: name,
643
734
  onChange: (e) => setName(e.target.value),
644
735
  placeholder: "Your name",
@@ -667,6 +758,7 @@ function SignupScreen() {
667
758
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
668
759
  "label",
669
760
  {
761
+ htmlFor: "afk-signup-email",
670
762
  style: {
671
763
  position: "absolute",
672
764
  top: "-10px",
@@ -683,6 +775,7 @@ function SignupScreen() {
683
775
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
684
776
  "input",
685
777
  {
778
+ id: "afk-signup-email",
686
779
  value: email,
687
780
  onChange: (e) => setEmail(e.target.value),
688
781
  type: "email",
@@ -712,6 +805,7 @@ function SignupScreen() {
712
805
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
713
806
  "label",
714
807
  {
808
+ htmlFor: "afk-signup-password",
715
809
  style: {
716
810
  position: "absolute",
717
811
  top: "-10px",
@@ -728,6 +822,7 @@ function SignupScreen() {
728
822
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
729
823
  "input",
730
824
  {
825
+ id: "afk-signup-password",
731
826
  value: password,
732
827
  onChange: (e) => setPassword(e.target.value),
733
828
  type: "password",
@@ -753,6 +848,95 @@ function SignupScreen() {
753
848
  }
754
849
  )
755
850
  ] }),
851
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { position: "relative", marginBottom: 18 }, children: [
852
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
853
+ "label",
854
+ {
855
+ htmlFor: "afk-signup-confirm-password",
856
+ style: {
857
+ position: "absolute",
858
+ top: "-10px",
859
+ left: "14px",
860
+ background: "rgba(255,255,255,0.85)",
861
+ padding: "0 6px",
862
+ fontSize: 13,
863
+ color: "#444",
864
+ borderRadius: 6
865
+ },
866
+ children: "Confirm password"
867
+ }
868
+ ),
869
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
870
+ "input",
871
+ {
872
+ id: "afk-signup-confirm-password",
873
+ value: confirmPassword,
874
+ onChange: (e) => setConfirmPassword(e.target.value),
875
+ type: "password",
876
+ placeholder: "Repeat your password",
877
+ style: {
878
+ width: "80%",
879
+ padding: "14px 16px",
880
+ borderRadius: 12,
881
+ border: "1px solid #d2d2d2",
882
+ fontSize: 15,
883
+ outline: "none",
884
+ transition: "0.25s",
885
+ background: "rgba(255,255,255,0.85)"
886
+ },
887
+ onFocus: (e) => {
888
+ e.currentTarget.style.border = "1px solid #4b4bff";
889
+ e.currentTarget.style.boxShadow = "0 0 0 3px rgba(75,75,255,0.25)";
890
+ },
891
+ onBlur: (e) => {
892
+ e.currentTarget.style.border = "1px solid #d2d2d2";
893
+ e.currentTarget.style.boxShadow = "0 0 0 transparent";
894
+ }
895
+ }
896
+ )
897
+ ] }),
898
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
899
+ "div",
900
+ {
901
+ style: {
902
+ marginBottom: 20,
903
+ fontSize: 12,
904
+ color: "#555"
905
+ },
906
+ children: [
907
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: { marginBottom: 6 }, children: "Use at least 8 characters, including letters, numbers, and symbols." }),
908
+ password && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
909
+ "p",
910
+ {
911
+ style: {
912
+ fontWeight: 600,
913
+ color: getPasswordStrength(password).color
914
+ },
915
+ children: [
916
+ "Password strength: ",
917
+ getPasswordStrength(password).label
918
+ ]
919
+ }
920
+ )
921
+ ]
922
+ }
923
+ ),
924
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
925
+ "p",
926
+ {
927
+ style: {
928
+ textAlign: "center",
929
+ fontSize: 13,
930
+ color: "#4b4bff",
931
+ cursor: "pointer",
932
+ marginBottom: 24
933
+ },
934
+ onClick: () => {
935
+ window.dispatchEvent(new CustomEvent("auth-flow-kit:navigate-to-login"));
936
+ },
937
+ children: "Already have an account? Sign in"
938
+ }
939
+ ),
756
940
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
757
941
  "button",
758
942
  {
@@ -778,6 +962,8 @@ function SignupScreen() {
778
962
  error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
779
963
  "p",
780
964
  {
965
+ role: "alert",
966
+ "aria-live": "polite",
781
967
  style: {
782
968
  marginTop: 18,
783
969
  color: "crimson",