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