@riligar/auth-react 1.3.0 → 1.5.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.
package/dist/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import React$1, { useEffect, useMemo, createContext, useState } from 'react';
1
+ import React$1, { useMemo, useEffect, createContext, useState, useRef } from 'react';
2
2
  import { Navigate, Outlet } from 'react-router-dom';
3
3
  import { Paper, Stack, Image, Title, Text, TextInput, PasswordInput, Anchor, Button, Divider, Group, Center, Loader } from '@mantine/core';
4
4
  import { useForm } from '@mantine/form';
@@ -58,10 +58,18 @@ async function api(route, opts = {}) {
58
58
 
59
59
  // Converte JSON automaticamente e lança erro legível
60
60
  const data = res.status !== 204 ? await res.json().catch(() => ({})) : null;
61
- if (!res.ok) throw Object.assign(new Error(data?.error ?? res.statusText), {
62
- res,
63
- data
64
- });
61
+ if (!res.ok) {
62
+ let errorMessage = data?.message || data?.error || data?.detail || res.statusText;
63
+
64
+ // Se a mensagem for um objeto, tenta extrair string ou stringify
65
+ if (typeof errorMessage === 'object') {
66
+ errorMessage = errorMessage.message || JSON.stringify(errorMessage);
67
+ }
68
+ throw Object.assign(new Error(errorMessage), {
69
+ res,
70
+ data
71
+ });
72
+ }
65
73
  return data;
66
74
  }
67
75
 
@@ -672,6 +680,68 @@ const useAuthStore = create((set, get) => ({
672
680
  })
673
681
  }));
674
682
 
683
+ const isIterable = (obj) => Symbol.iterator in obj;
684
+ const hasIterableEntries = (value) => (
685
+ // HACK: avoid checking entries type
686
+ "entries" in value
687
+ );
688
+ const compareEntries = (valueA, valueB) => {
689
+ const mapA = valueA instanceof Map ? valueA : new Map(valueA.entries());
690
+ const mapB = valueB instanceof Map ? valueB : new Map(valueB.entries());
691
+ if (mapA.size !== mapB.size) {
692
+ return false;
693
+ }
694
+ for (const [key, value] of mapA) {
695
+ if (!mapB.has(key) || !Object.is(value, mapB.get(key))) {
696
+ return false;
697
+ }
698
+ }
699
+ return true;
700
+ };
701
+ const compareIterables = (valueA, valueB) => {
702
+ const iteratorA = valueA[Symbol.iterator]();
703
+ const iteratorB = valueB[Symbol.iterator]();
704
+ let nextA = iteratorA.next();
705
+ let nextB = iteratorB.next();
706
+ while (!nextA.done && !nextB.done) {
707
+ if (!Object.is(nextA.value, nextB.value)) {
708
+ return false;
709
+ }
710
+ nextA = iteratorA.next();
711
+ nextB = iteratorB.next();
712
+ }
713
+ return !!nextA.done && !!nextB.done;
714
+ };
715
+ function shallow(valueA, valueB) {
716
+ if (Object.is(valueA, valueB)) {
717
+ return true;
718
+ }
719
+ if (typeof valueA !== "object" || valueA === null || typeof valueB !== "object" || valueB === null) {
720
+ return false;
721
+ }
722
+ if (Object.getPrototypeOf(valueA) !== Object.getPrototypeOf(valueB)) {
723
+ return false;
724
+ }
725
+ if (isIterable(valueA) && isIterable(valueB)) {
726
+ if (hasIterableEntries(valueA) && hasIterableEntries(valueB)) {
727
+ return compareEntries(valueA, valueB);
728
+ }
729
+ return compareIterables(valueA, valueB);
730
+ }
731
+ return compareEntries(
732
+ { entries: () => Object.entries(valueA) },
733
+ { entries: () => Object.entries(valueB) }
734
+ );
735
+ }
736
+
737
+ function useShallow(selector) {
738
+ const prev = React$1.useRef(void 0);
739
+ return (state) => {
740
+ const next = selector(state);
741
+ return shallow(prev.current, next) ? prev.current : prev.current = next;
742
+ };
743
+ }
744
+
675
745
  const AuthContext = /*#__PURE__*/createContext(); // só para ter o Provider em JSX
676
746
 
677
747
  function AuthProvider({
@@ -694,7 +764,9 @@ function AuthProvider({
694
764
  const checkTokenValidity = useAuthStore(s => s.checkTokenValidity);
695
765
 
696
766
  // Configura SDK com apiUrl e apiKey
697
- useEffect(() => {
767
+ // Configura SDK com apiUrl e apiKey
768
+ // Usamos useMemo para garantir que a configuração ocorra ANTES dos efeitos dos componentes filhos
769
+ useMemo(() => {
698
770
  configure({
699
771
  apiUrl,
700
772
  apiKey
@@ -741,12 +813,12 @@ function AuthProvider({
741
813
  }
742
814
 
743
815
  /* Hooks "facade" que a app vai usar */
744
- const useAuth = () => useAuthStore(s => ({
816
+ const useAuth = () => useAuthStore(useShallow(s => ({
745
817
  user: s.user,
746
818
  loading: s.loading,
747
819
  error: s.error,
748
820
  isAuthenticated: s.user !== null
749
- }));
821
+ })));
750
822
 
751
823
  // Auth Actions
752
824
  const useSignIn = () => useAuthStore(s => s.signIn);
@@ -755,62 +827,37 @@ const useSignOut = () => useAuthStore(s => s.signOut);
755
827
  const useCheckToken = () => useAuthStore(s => s.checkTokenValidity);
756
828
 
757
829
  // Magic Link Hook
758
- const useMagicLink = () => {
759
- const sendMagicLink = useAuthStore(s => s.sendMagicLink);
760
- const verifyMagicLink = useAuthStore(s => s.verifyMagicLink);
761
- const loading = useAuthStore(s => s.loadingStates.magicLink);
762
- const verifying = useAuthStore(s => s.loadingStates.verifyMagicLink);
763
- const error = useAuthStore(s => s.error);
764
- return {
765
- sendMagicLink,
766
- verifyMagicLink,
767
- loading,
768
- verifying,
769
- error
770
- };
771
- };
830
+ const useMagicLink = () => useAuthStore(useShallow(s => ({
831
+ sendMagicLink: s.sendMagicLink,
832
+ verifyMagicLink: s.verifyMagicLink,
833
+ loading: s.loadingStates.magicLink,
834
+ verifying: s.loadingStates.verifyMagicLink,
835
+ error: s.error
836
+ })));
772
837
 
773
838
  // Password Reset Hook
774
- const usePasswordReset = () => {
775
- const forgotPassword = useAuthStore(s => s.forgotPassword);
776
- const resetPassword = useAuthStore(s => s.resetPassword);
777
- const loading = useAuthStore(s => s.loadingStates.resetPassword);
778
- const error = useAuthStore(s => s.error);
779
- return {
780
- forgotPassword,
781
- resetPassword,
782
- loading,
783
- error
784
- };
785
- };
839
+ const usePasswordReset = () => useAuthStore(useShallow(s => ({
840
+ forgotPassword: s.forgotPassword,
841
+ resetPassword: s.resetPassword,
842
+ loading: s.loadingStates.resetPassword,
843
+ error: s.error
844
+ })));
786
845
 
787
846
  // Email Verification Hook
788
- const useEmailVerification = () => {
789
- const verifyEmail = useAuthStore(s => s.verifyEmail);
790
- const resendVerification = useAuthStore(s => s.resendVerification);
791
- const loading = useAuthStore(s => s.loadingStates.verifyEmail);
792
- const resending = useAuthStore(s => s.loadingStates.resendVerification);
793
- const error = useAuthStore(s => s.error);
794
- return {
795
- verifyEmail,
796
- resendVerification,
797
- loading,
798
- resending,
799
- error
800
- };
801
- };
847
+ const useEmailVerification = () => useAuthStore(useShallow(s => ({
848
+ verifyEmail: s.verifyEmail,
849
+ resendVerification: s.resendVerification,
850
+ loading: s.loadingStates.verifyEmail,
851
+ resending: s.loadingStates.resendVerification,
852
+ error: s.error
853
+ })));
802
854
 
803
855
  // Session Hook
804
- const useSession = () => {
805
- const getSession = useAuthStore(s => s.getSession);
806
- const user = useAuthStore(s => s.user);
807
- const setUser = useAuthStore(s => s.setUser);
808
- return {
809
- getSession,
810
- user,
811
- setUser
812
- };
813
- };
856
+ const useSession = () => useAuthStore(useShallow(s => ({
857
+ getSession: s.getSession,
858
+ user: s.user,
859
+ setUser: s.setUser
860
+ })));
814
861
 
815
862
  // Loading States Hook
816
863
  const useAuthLoading = () => useAuthStore(s => s.loadingStates);
@@ -1237,6 +1284,7 @@ function MagicLinkVerify({
1237
1284
  const [status, setStatus] = useState('verifying'); // verifying, success, error
1238
1285
  const [errorMessage, setErrorMessage] = useState('');
1239
1286
  const verifyMagicLink = useAuthStore(s => s.verifyMagicLink);
1287
+ const verifyingTokenRef = useRef(null);
1240
1288
  useEffect(() => {
1241
1289
  const verify = async () => {
1242
1290
  // Pega token da prop ou da URL
@@ -1251,6 +1299,10 @@ function MagicLinkVerify({
1251
1299
  onError?.(new Error('No token'));
1252
1300
  return;
1253
1301
  }
1302
+
1303
+ // Evita verificar o mesmo token duas vezes (React Strict Mode ou re-renders)
1304
+ if (verifyingTokenRef.current === token) return;
1305
+ verifyingTokenRef.current = token;
1254
1306
  try {
1255
1307
  const result = await verifyMagicLink(token);
1256
1308
  setStatus('success');
@@ -1267,7 +1319,8 @@ function MagicLinkVerify({
1267
1319
  }
1268
1320
  };
1269
1321
  verify();
1270
- }, [propToken, verifyMagicLink, onSuccess, onError, redirectTo, redirectDelay, labels]);
1322
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1323
+ }, [propToken, redirectTo, redirectDelay]);
1271
1324
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1272
1325
  logo: logo
1273
1326
  }, cardProps), status === 'verifying' && /*#__PURE__*/React.createElement(Stack, {
@@ -1522,9 +1575,13 @@ function VerifyEmailCard({
1522
1575
  }) {
1523
1576
  const [status, setStatus] = useState('verifying'); // verifying, success, error
1524
1577
  const [errorMessage, setErrorMessage] = useState('');
1578
+ const [tokenEmail, setTokenEmail] = useState(null); // Novo estado para email do token
1579
+
1525
1580
  const verifyEmail = useAuthStore(s => s.verifyEmail);
1526
1581
  const resendVerification = useAuthStore(s => s.resendVerification);
1527
1582
  const loadingResend = useAuthStore(s => s.loadingStates.resendVerification);
1583
+ const user = useAuthStore(s => s.user);
1584
+ const verifyingTokenRef = useRef(null);
1528
1585
  useEffect(() => {
1529
1586
  const verify = async () => {
1530
1587
  // Pega token da prop ou da URL
@@ -1534,11 +1591,25 @@ function VerifyEmailCard({
1534
1591
  token = urlParams.get('token');
1535
1592
  }
1536
1593
  if (!token) {
1537
- setStatus('error');
1538
- setErrorMessage(labels.noToken || 'Token não encontrado na URL');
1539
- onError?.(new Error('No token'));
1594
+ // Se já mostramos o erro de token faltando, não precisa fazer nada
1595
+ if (status === 'missing_token') return;
1596
+ setStatus('missing_token');
1540
1597
  return;
1541
1598
  }
1599
+
1600
+ // Tenta extrair email do token (mesmo expirado) para permitir reenvio
1601
+ try {
1602
+ const payload = decodeJWT(token);
1603
+ if (payload?.email) {
1604
+ setTokenEmail(payload.email);
1605
+ }
1606
+ } catch (e) {
1607
+ // ignore jwt error
1608
+ }
1609
+
1610
+ // Evita verificar o mesmo token duas vezes (React Strict Mode ou re-renders)
1611
+ if (verifyingTokenRef.current === token) return;
1612
+ verifyingTokenRef.current = token;
1542
1613
  try {
1543
1614
  const result = await verifyEmail(token);
1544
1615
  setStatus('success');
@@ -1549,18 +1620,22 @@ function VerifyEmailCard({
1549
1620
  }, redirectDelay);
1550
1621
  }
1551
1622
  } catch (error) {
1623
+ // Se der erro, permitimos tentar novamente apenas se o usuário recarregar
1624
+ // ou se implementarmos um botão de retry que limpe o ref
1552
1625
  setStatus('error');
1553
1626
  setErrorMessage(error.message || labels.verificationFailed || 'Verificação falhou');
1554
1627
  onError?.(error);
1555
1628
  }
1556
1629
  };
1557
1630
  verify();
1558
- }, [propToken, verifyEmail, onSuccess, onError, redirectTo, redirectDelay, labels]);
1631
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1632
+ }, [propToken, redirectTo, redirectDelay]);
1559
1633
  const handleResend = async () => {
1560
- if (!email) return;
1634
+ const targetEmail = email || user?.email || tokenEmail;
1635
+ if (!targetEmail) return;
1561
1636
  try {
1562
- await resendVerification(email);
1563
- onResent?.(email);
1637
+ await resendVerification(targetEmail);
1638
+ onResent?.(targetEmail);
1564
1639
  } catch (error) {
1565
1640
  onError?.(error);
1566
1641
  }
@@ -1578,7 +1653,19 @@ function VerifyEmailCard({
1578
1653
  size: "sm",
1579
1654
  c: "dimmed",
1580
1655
  ta: "center"
1581
- }, labels.pleaseWait || 'Aguarde enquanto verificamos seu email...')), status === 'success' && /*#__PURE__*/React.createElement(Stack, {
1656
+ }, labels.pleaseWait || 'Aguarde enquanto verificamos seu email...')), status === 'missing_token' && /*#__PURE__*/React.createElement(Stack, {
1657
+ align: "center",
1658
+ gap: "sm"
1659
+ }, /*#__PURE__*/React.createElement(IconMail, {
1660
+ size: 48,
1661
+ color: "var(--mantine-color-blue-6)"
1662
+ }), /*#__PURE__*/React.createElement(Title, {
1663
+ order: 4
1664
+ }, labels.verification || 'Verificação de Email'), /*#__PURE__*/React.createElement(Text, {
1665
+ size: "sm",
1666
+ c: "dimmed",
1667
+ ta: "center"
1668
+ }, labels.missingToken || 'Para verificar seu conta, clique no link enviado para seu email.')), status === 'success' && /*#__PURE__*/React.createElement(Stack, {
1582
1669
  align: "center",
1583
1670
  gap: "sm"
1584
1671
  }, /*#__PURE__*/React.createElement(IconCheck, {
@@ -1602,7 +1689,7 @@ function VerifyEmailCard({
1602
1689
  size: "sm",
1603
1690
  c: "dimmed",
1604
1691
  ta: "center"
1605
- }, errorMessage || labels.invalidToken || 'O link é inválido ou expirou.'), email && /*#__PURE__*/React.createElement(Button, {
1692
+ }, errorMessage || labels.invalidToken || 'O link é inválido ou expirou.'), (email || user?.email || tokenEmail) && errorMessage?.toLowerCase().includes('expired') && /*#__PURE__*/React.createElement(Button, {
1606
1693
  variant: "light",
1607
1694
  leftSection: /*#__PURE__*/React.createElement(IconRefresh, {
1608
1695
  size: 16
@@ -1610,8 +1697,8 @@ function VerifyEmailCard({
1610
1697
  onClick: handleResend,
1611
1698
  loading: loadingResend,
1612
1699
  fullWidth: true
1613
- }, labels.resend || 'Reenviar Email de Verificação')));
1700
+ }, labels.resend || 'Solicitar novo link')));
1614
1701
  }
1615
1702
 
1616
- export { AuthCard, AuthProvider, ForgotPasswordForm, MagicLinkForm, MagicLinkVerify, ProtectedRoute, ResetPasswordForm, SignInForm, SignUpForm, VerifyEmailCard, configure, forgotPassword, getCurrentUser, getSession, isAuthenticated, refreshToken, resendVerification, resetPassword, sendMagicLink, signIn, signOut, signUp, socialRedirect, useAuth, useAuthLoading, useAuthStore, useCheckToken, useEmailVerification, useMagicLink, usePasswordReset, useSession, useSignIn, useSignOut, useSignUp, verifyEmail, verifyMagicLink };
1703
+ export { AuthCard, AuthProvider, ForgotPasswordForm, MagicLinkForm, MagicLinkVerify, ProtectedRoute, ResetPasswordForm, SignInForm, SignUpForm, VerifyEmailCard, configure, decodeJWT, forgotPassword, getCurrentUser, getSession, isAuthenticated, refreshToken, resendVerification, resetPassword, sendMagicLink, signIn, signOut, signUp, socialRedirect, useAuth, useAuthLoading, useAuthStore, useCheckToken, useEmailVerification, useMagicLink, usePasswordReset, useSession, useSignIn, useSignOut, useSignUp, verifyEmail, verifyMagicLink };
1617
1704
  //# sourceMappingURL=index.esm.js.map