@riligar/auth-react 1.4.0 → 1.6.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
 
@@ -263,6 +271,17 @@ const getSession = async () => {
263
271
  return await api('/auth/session');
264
272
  };
265
273
 
274
+ /*--- Application Info ----------------------------*/
275
+ const getApplicationInfo = async () => {
276
+ try {
277
+ const data = await api('/application/by-api-key');
278
+ return data?.data || null;
279
+ } catch (error) {
280
+ console.warn('[AuthSDK] Failed to fetch application info:', error.message);
281
+ return null;
282
+ }
283
+ };
284
+
266
285
  /* Social login redirect (ex.: Google) -----------*/
267
286
  function socialRedirect(provider, redirectTo = typeof window !== 'undefined' ? window.location.href : '/') {
268
287
  if (typeof window === 'undefined') return;
@@ -326,6 +345,8 @@ const useAuthStore = create((set, get) => ({
326
345
  verifyEmail: false,
327
346
  resendVerification: false
328
347
  },
348
+ // Application info (logo, nome, etc)
349
+ applicationInfo: null,
329
350
  // Helper para atualizar loading states
330
351
  setLoading: (key, value) => set(state => ({
331
352
  loadingStates: {
@@ -333,9 +354,29 @@ const useAuthStore = create((set, get) => ({
333
354
  [key]: value
334
355
  }
335
356
  })),
357
+ // Buscar informações da aplicação
358
+ fetchApplicationInfo: async () => {
359
+ try {
360
+ const appInfo = await getApplicationInfo();
361
+ set({
362
+ applicationInfo: appInfo
363
+ });
364
+ } catch (error) {
365
+ console.warn('[AuthStore] Failed to fetch application info:', error);
366
+ set({
367
+ applicationInfo: null
368
+ });
369
+ }
370
+ },
336
371
  /* Init ao montar o Provider */
337
372
  init: async () => {
373
+ const {
374
+ fetchApplicationInfo
375
+ } = get();
338
376
  try {
377
+ // Buscar application info primeiro (não bloqueia o init)
378
+ fetchApplicationInfo();
379
+
339
380
  // Verifica se há um token válido
340
381
  if (isAuthenticated()) {
341
382
  // Tenta extrair usuário do token (JWT)
@@ -756,7 +797,9 @@ function AuthProvider({
756
797
  const checkTokenValidity = useAuthStore(s => s.checkTokenValidity);
757
798
 
758
799
  // Configura SDK com apiUrl e apiKey
759
- useEffect(() => {
800
+ // Configura SDK com apiUrl e apiKey
801
+ // Usamos useMemo para garantir que a configuração ocorra ANTES dos efeitos dos componentes filhos
802
+ useMemo(() => {
760
803
  configure({
761
804
  apiUrl,
762
805
  apiKey
@@ -852,6 +895,13 @@ const useSession = () => useAuthStore(useShallow(s => ({
852
895
  // Loading States Hook
853
896
  const useAuthLoading = () => useAuthStore(s => s.loadingStates);
854
897
 
898
+ // Application Logo Hook
899
+ const useApplicationLogo = () => {
900
+ const applicationInfo = useAuthStore(s => s.applicationInfo);
901
+ // Retorna o logo da aplicação ou null (componentes usam fallback padrão)
902
+ return applicationInfo?.image || null;
903
+ };
904
+
855
905
  function ProtectedRoute({
856
906
  fallback = /*#__PURE__*/React.createElement("p", null, "\u231B Carregando..."),
857
907
  redirectTo = "/login"
@@ -887,7 +937,7 @@ function AuthCard({
887
937
  title,
888
938
  subtitle,
889
939
  logo,
890
- logoHeight = 32,
940
+ logoHeight = 28,
891
941
  width = 350,
892
942
  ...props
893
943
  }) {
@@ -922,7 +972,8 @@ var img = "data:image/webp;base64,iVBORw0KGgoAAAANSUhEUgAAAjwAAADICAYAAADskzu8AA
922
972
  */
923
973
  function SignInForm({
924
974
  // Configuração
925
- logo = img,
975
+ logo,
976
+ // Removido default, será calculado abaixo
926
977
  title = 'Entrar',
927
978
  subtitle = 'Acesse sua conta para continuar',
928
979
  // Features
@@ -948,6 +999,10 @@ function SignInForm({
948
999
  const loadingSignIn = useAuthStore(s => s.loadingStates.signIn);
949
1000
  const loadingMagicLink = useAuthStore(s => s.loadingStates.magicLink);
950
1001
  const loadingResetPassword = useAuthStore(s => s.loadingStates.resetPassword);
1002
+
1003
+ // Hook para buscar logo da aplicação
1004
+ const applicationLogo = useApplicationLogo();
1005
+ const finalLogo = logo || applicationLogo || img;
951
1006
  const form = useForm({
952
1007
  initialValues: {
953
1008
  email: '',
@@ -992,7 +1047,7 @@ function SignInForm({
992
1047
  };
993
1048
  const isLoading = loadingSignIn || loadingMagicLink || loadingResetPassword;
994
1049
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
995
- logo: logo,
1050
+ logo: finalLogo,
996
1051
  title: title,
997
1052
  subtitle: subtitle
998
1053
  }, cardProps), /*#__PURE__*/React.createElement("form", {
@@ -1070,7 +1125,7 @@ function SignInForm({
1070
1125
  */
1071
1126
  function SignUpForm({
1072
1127
  // Configuração
1073
- logo = img,
1128
+ logo,
1074
1129
  title = 'Criar Conta',
1075
1130
  subtitle = 'Preencha os dados para se cadastrar',
1076
1131
  // Features
@@ -1089,6 +1144,10 @@ function SignUpForm({
1089
1144
  }) {
1090
1145
  const signUp = useAuthStore(s => s.signUp);
1091
1146
  const loading = useAuthStore(s => s.loadingStates.signUp);
1147
+
1148
+ // Hook para buscar logo da aplicação
1149
+ const applicationLogo = useApplicationLogo();
1150
+ const finalLogo = logo || applicationLogo || img;
1092
1151
  const form = useForm({
1093
1152
  initialValues: {
1094
1153
  name: '',
@@ -1112,7 +1171,7 @@ function SignUpForm({
1112
1171
  }
1113
1172
  };
1114
1173
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1115
- logo: logo,
1174
+ logo: finalLogo,
1116
1175
  title: title,
1117
1176
  subtitle: subtitle
1118
1177
  }, cardProps), /*#__PURE__*/React.createElement("form", {
@@ -1189,7 +1248,7 @@ function SignUpForm({
1189
1248
  */
1190
1249
  function MagicLinkForm({
1191
1250
  // Configuração
1192
- logo = img,
1251
+ logo,
1193
1252
  title = 'Login sem Senha',
1194
1253
  subtitle = 'Receba um link de acesso no seu email',
1195
1254
  // Features
@@ -1205,6 +1264,10 @@ function MagicLinkForm({
1205
1264
  }) {
1206
1265
  const sendMagicLink = useAuthStore(s => s.sendMagicLink);
1207
1266
  const loading = useAuthStore(s => s.loadingStates.magicLink);
1267
+
1268
+ // Hook para buscar logo da aplicação
1269
+ const applicationLogo = useApplicationLogo();
1270
+ const finalLogo = logo || applicationLogo || img;
1208
1271
  const form = useForm({
1209
1272
  initialValues: {
1210
1273
  email: ''
@@ -1222,7 +1285,7 @@ function MagicLinkForm({
1222
1285
  }
1223
1286
  };
1224
1287
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1225
- logo: logo,
1288
+ logo: finalLogo,
1226
1289
  title: title,
1227
1290
  subtitle: subtitle
1228
1291
  }, cardProps), /*#__PURE__*/React.createElement("form", {
@@ -1258,7 +1321,7 @@ function MagicLinkForm({
1258
1321
  */
1259
1322
  function MagicLinkVerify({
1260
1323
  // Configuração
1261
- logo = img,
1324
+ logo,
1262
1325
  // Token pode ser passado diretamente ou extraído da URL
1263
1326
  token: propToken,
1264
1327
  // Callbacks
@@ -1274,6 +1337,11 @@ function MagicLinkVerify({
1274
1337
  const [status, setStatus] = useState('verifying'); // verifying, success, error
1275
1338
  const [errorMessage, setErrorMessage] = useState('');
1276
1339
  const verifyMagicLink = useAuthStore(s => s.verifyMagicLink);
1340
+
1341
+ // Hook para buscar logo da aplicação
1342
+ const applicationLogo = useApplicationLogo();
1343
+ const finalLogo = logo || applicationLogo || img;
1344
+ const verifyingTokenRef = useRef(null);
1277
1345
  useEffect(() => {
1278
1346
  const verify = async () => {
1279
1347
  // Pega token da prop ou da URL
@@ -1288,6 +1356,10 @@ function MagicLinkVerify({
1288
1356
  onError?.(new Error('No token'));
1289
1357
  return;
1290
1358
  }
1359
+
1360
+ // Evita verificar o mesmo token duas vezes (React Strict Mode ou re-renders)
1361
+ if (verifyingTokenRef.current === token) return;
1362
+ verifyingTokenRef.current = token;
1291
1363
  try {
1292
1364
  const result = await verifyMagicLink(token);
1293
1365
  setStatus('success');
@@ -1304,9 +1376,10 @@ function MagicLinkVerify({
1304
1376
  }
1305
1377
  };
1306
1378
  verify();
1307
- }, [propToken, verifyMagicLink, onSuccess, onError, redirectTo, redirectDelay, labels]);
1379
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1380
+ }, [propToken, redirectTo, redirectDelay]);
1308
1381
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1309
- logo: logo
1382
+ logo: finalLogo
1310
1383
  }, cardProps), status === 'verifying' && /*#__PURE__*/React.createElement(Stack, {
1311
1384
  align: "center",
1312
1385
  gap: "sm"
@@ -1350,7 +1423,7 @@ function MagicLinkVerify({
1350
1423
  */
1351
1424
  function ForgotPasswordForm({
1352
1425
  // Configuração
1353
- logo = img,
1426
+ logo,
1354
1427
  title = 'Recuperar Senha',
1355
1428
  subtitle = 'Enviaremos um link para redefinir sua senha',
1356
1429
  // Features
@@ -1366,6 +1439,10 @@ function ForgotPasswordForm({
1366
1439
  }) {
1367
1440
  const forgotPassword = useAuthStore(s => s.forgotPassword);
1368
1441
  const loading = useAuthStore(s => s.loadingStates.resetPassword);
1442
+
1443
+ // Hook para buscar logo da aplicação
1444
+ const applicationLogo = useApplicationLogo();
1445
+ const finalLogo = logo || applicationLogo || img;
1369
1446
  const form = useForm({
1370
1447
  initialValues: {
1371
1448
  email: ''
@@ -1383,7 +1460,7 @@ function ForgotPasswordForm({
1383
1460
  }
1384
1461
  };
1385
1462
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1386
- logo: logo,
1463
+ logo: finalLogo,
1387
1464
  title: title,
1388
1465
  subtitle: subtitle
1389
1466
  }, cardProps), /*#__PURE__*/React.createElement("form", {
@@ -1418,7 +1495,7 @@ function ForgotPasswordForm({
1418
1495
  */
1419
1496
  function ResetPasswordForm({
1420
1497
  // Configuração
1421
- logo = img,
1498
+ logo,
1422
1499
  title = 'Nova Senha',
1423
1500
  subtitle = 'Crie uma nova senha para sua conta',
1424
1501
  // Token pode ser passado diretamente ou extraído da URL
@@ -1437,6 +1514,10 @@ function ResetPasswordForm({
1437
1514
  const resetPassword = useAuthStore(s => s.resetPassword);
1438
1515
  const loading = useAuthStore(s => s.loadingStates.resetPassword);
1439
1516
 
1517
+ // Hook para buscar logo da aplicação
1518
+ const applicationLogo = useApplicationLogo();
1519
+ const finalLogo = logo || applicationLogo || img;
1520
+
1440
1521
  // Extrai token da URL se não foi passado como prop
1441
1522
  useEffect(() => {
1442
1523
  if (!propToken && typeof window !== 'undefined') {
@@ -1470,7 +1551,7 @@ function ResetPasswordForm({
1470
1551
  };
1471
1552
  if (success) {
1472
1553
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1473
- logo: logo
1554
+ logo: finalLogo
1474
1555
  }, cardProps), /*#__PURE__*/React.createElement(Stack, {
1475
1556
  align: "center",
1476
1557
  gap: "sm"
@@ -1491,7 +1572,7 @@ function ResetPasswordForm({
1491
1572
  }
1492
1573
  if (!token) {
1493
1574
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1494
- logo: logo
1575
+ logo: finalLogo
1495
1576
  }, cardProps), /*#__PURE__*/React.createElement(Stack, {
1496
1577
  align: "center",
1497
1578
  gap: "sm"
@@ -1505,7 +1586,7 @@ function ResetPasswordForm({
1505
1586
  }, labels.goToSignIn || 'Voltar para Login')));
1506
1587
  }
1507
1588
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1508
- logo: logo,
1589
+ logo: finalLogo,
1509
1590
  title: title,
1510
1591
  subtitle: subtitle
1511
1592
  }, cardProps), /*#__PURE__*/React.createElement("form", {
@@ -1541,7 +1622,7 @@ function ResetPasswordForm({
1541
1622
  */
1542
1623
  function VerifyEmailCard({
1543
1624
  // Configuração
1544
- logo = img,
1625
+ logo,
1545
1626
  // Token pode ser passado diretamente ou extraído da URL
1546
1627
  token: propToken,
1547
1628
  // Email para reenvio (opcional)
@@ -1559,9 +1640,17 @@ function VerifyEmailCard({
1559
1640
  }) {
1560
1641
  const [status, setStatus] = useState('verifying'); // verifying, success, error
1561
1642
  const [errorMessage, setErrorMessage] = useState('');
1643
+ const [tokenEmail, setTokenEmail] = useState(null); // Novo estado para email do token
1644
+
1562
1645
  const verifyEmail = useAuthStore(s => s.verifyEmail);
1563
1646
  const resendVerification = useAuthStore(s => s.resendVerification);
1564
1647
  const loadingResend = useAuthStore(s => s.loadingStates.resendVerification);
1648
+ const user = useAuthStore(s => s.user);
1649
+ const verifyingTokenRef = useRef(null);
1650
+
1651
+ // Hook para buscar logo da aplicação
1652
+ const applicationLogo = useApplicationLogo();
1653
+ const finalLogo = logo || applicationLogo || img;
1565
1654
  useEffect(() => {
1566
1655
  const verify = async () => {
1567
1656
  // Pega token da prop ou da URL
@@ -1571,11 +1660,25 @@ function VerifyEmailCard({
1571
1660
  token = urlParams.get('token');
1572
1661
  }
1573
1662
  if (!token) {
1574
- setStatus('error');
1575
- setErrorMessage(labels.noToken || 'Token não encontrado na URL');
1576
- onError?.(new Error('No token'));
1663
+ // Se já mostramos o erro de token faltando, não precisa fazer nada
1664
+ if (status === 'missing_token') return;
1665
+ setStatus('missing_token');
1577
1666
  return;
1578
1667
  }
1668
+
1669
+ // Tenta extrair email do token (mesmo expirado) para permitir reenvio
1670
+ try {
1671
+ const payload = decodeJWT(token);
1672
+ if (payload?.email) {
1673
+ setTokenEmail(payload.email);
1674
+ }
1675
+ } catch (e) {
1676
+ // ignore jwt error
1677
+ }
1678
+
1679
+ // Evita verificar o mesmo token duas vezes (React Strict Mode ou re-renders)
1680
+ if (verifyingTokenRef.current === token) return;
1681
+ verifyingTokenRef.current = token;
1579
1682
  try {
1580
1683
  const result = await verifyEmail(token);
1581
1684
  setStatus('success');
@@ -1586,24 +1689,28 @@ function VerifyEmailCard({
1586
1689
  }, redirectDelay);
1587
1690
  }
1588
1691
  } catch (error) {
1692
+ // Se der erro, permitimos tentar novamente apenas se o usuário recarregar
1693
+ // ou se implementarmos um botão de retry que limpe o ref
1589
1694
  setStatus('error');
1590
1695
  setErrorMessage(error.message || labels.verificationFailed || 'Verificação falhou');
1591
1696
  onError?.(error);
1592
1697
  }
1593
1698
  };
1594
1699
  verify();
1595
- }, [propToken, verifyEmail, onSuccess, onError, redirectTo, redirectDelay, labels]);
1700
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1701
+ }, [propToken, redirectTo, redirectDelay]);
1596
1702
  const handleResend = async () => {
1597
- if (!email) return;
1703
+ const targetEmail = email || user?.email || tokenEmail;
1704
+ if (!targetEmail) return;
1598
1705
  try {
1599
- await resendVerification(email);
1600
- onResent?.(email);
1706
+ await resendVerification(targetEmail);
1707
+ onResent?.(targetEmail);
1601
1708
  } catch (error) {
1602
1709
  onError?.(error);
1603
1710
  }
1604
1711
  };
1605
1712
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1606
- logo: logo
1713
+ logo: finalLogo
1607
1714
  }, cardProps), status === 'verifying' && /*#__PURE__*/React.createElement(Stack, {
1608
1715
  align: "center",
1609
1716
  gap: "sm"
@@ -1615,7 +1722,19 @@ function VerifyEmailCard({
1615
1722
  size: "sm",
1616
1723
  c: "dimmed",
1617
1724
  ta: "center"
1618
- }, labels.pleaseWait || 'Aguarde enquanto verificamos seu email...')), status === 'success' && /*#__PURE__*/React.createElement(Stack, {
1725
+ }, labels.pleaseWait || 'Aguarde enquanto verificamos seu email...')), status === 'missing_token' && /*#__PURE__*/React.createElement(Stack, {
1726
+ align: "center",
1727
+ gap: "sm"
1728
+ }, /*#__PURE__*/React.createElement(IconMail, {
1729
+ size: 48,
1730
+ color: "var(--mantine-color-blue-6)"
1731
+ }), /*#__PURE__*/React.createElement(Title, {
1732
+ order: 4
1733
+ }, labels.verification || 'Verificação de Email'), /*#__PURE__*/React.createElement(Text, {
1734
+ size: "sm",
1735
+ c: "dimmed",
1736
+ ta: "center"
1737
+ }, labels.missingToken || 'Para verificar seu conta, clique no link enviado para seu email.')), status === 'success' && /*#__PURE__*/React.createElement(Stack, {
1619
1738
  align: "center",
1620
1739
  gap: "sm"
1621
1740
  }, /*#__PURE__*/React.createElement(IconCheck, {
@@ -1639,7 +1758,7 @@ function VerifyEmailCard({
1639
1758
  size: "sm",
1640
1759
  c: "dimmed",
1641
1760
  ta: "center"
1642
- }, errorMessage || labels.invalidToken || 'O link é inválido ou expirou.'), email && /*#__PURE__*/React.createElement(Button, {
1761
+ }, errorMessage || labels.invalidToken || 'O link é inválido ou expirou.'), (email || user?.email || tokenEmail) && errorMessage?.toLowerCase().includes('expired') && /*#__PURE__*/React.createElement(Button, {
1643
1762
  variant: "light",
1644
1763
  leftSection: /*#__PURE__*/React.createElement(IconRefresh, {
1645
1764
  size: 16
@@ -1647,8 +1766,8 @@ function VerifyEmailCard({
1647
1766
  onClick: handleResend,
1648
1767
  loading: loadingResend,
1649
1768
  fullWidth: true
1650
- }, labels.resend || 'Reenviar Email de Verificação')));
1769
+ }, labels.resend || 'Solicitar novo link')));
1651
1770
  }
1652
1771
 
1653
- 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 };
1772
+ export { AuthCard, AuthProvider, ForgotPasswordForm, MagicLinkForm, MagicLinkVerify, ProtectedRoute, ResetPasswordForm, SignInForm, SignUpForm, VerifyEmailCard, configure, decodeJWT, forgotPassword, getApplicationInfo, getCurrentUser, getSession, isAuthenticated, refreshToken, resendVerification, resetPassword, sendMagicLink, signIn, signOut, signUp, socialRedirect, useAuth, useAuthLoading, useAuthStore, useCheckToken, useEmailVerification, useMagicLink, usePasswordReset, useSession, useSignIn, useSignOut, useSignUp, verifyEmail, verifyMagicLink };
1654
1773
  //# sourceMappingURL=index.esm.js.map