@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.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
 
@@ -266,6 +274,17 @@ const getSession = async () => {
266
274
  return await api('/auth/session');
267
275
  };
268
276
 
277
+ /*--- Application Info ----------------------------*/
278
+ const getApplicationInfo = async () => {
279
+ try {
280
+ const data = await api('/application/by-api-key');
281
+ return data?.data || null;
282
+ } catch (error) {
283
+ console.warn('[AuthSDK] Failed to fetch application info:', error.message);
284
+ return null;
285
+ }
286
+ };
287
+
269
288
  /* Social login redirect (ex.: Google) -----------*/
270
289
  function socialRedirect(provider, redirectTo = typeof window !== 'undefined' ? window.location.href : '/') {
271
290
  if (typeof window === 'undefined') return;
@@ -329,6 +348,8 @@ const useAuthStore = create((set, get) => ({
329
348
  verifyEmail: false,
330
349
  resendVerification: false
331
350
  },
351
+ // Application info (logo, nome, etc)
352
+ applicationInfo: null,
332
353
  // Helper para atualizar loading states
333
354
  setLoading: (key, value) => set(state => ({
334
355
  loadingStates: {
@@ -336,9 +357,29 @@ const useAuthStore = create((set, get) => ({
336
357
  [key]: value
337
358
  }
338
359
  })),
360
+ // Buscar informações da aplicação
361
+ fetchApplicationInfo: async () => {
362
+ try {
363
+ const appInfo = await getApplicationInfo();
364
+ set({
365
+ applicationInfo: appInfo
366
+ });
367
+ } catch (error) {
368
+ console.warn('[AuthStore] Failed to fetch application info:', error);
369
+ set({
370
+ applicationInfo: null
371
+ });
372
+ }
373
+ },
339
374
  /* Init ao montar o Provider */
340
375
  init: async () => {
376
+ const {
377
+ fetchApplicationInfo
378
+ } = get();
341
379
  try {
380
+ // Buscar application info primeiro (não bloqueia o init)
381
+ fetchApplicationInfo();
382
+
342
383
  // Verifica se há um token válido
343
384
  if (isAuthenticated()) {
344
385
  // Tenta extrair usuário do token (JWT)
@@ -759,7 +800,9 @@ function AuthProvider({
759
800
  const checkTokenValidity = useAuthStore(s => s.checkTokenValidity);
760
801
 
761
802
  // Configura SDK com apiUrl e apiKey
762
- React$1.useEffect(() => {
803
+ // Configura SDK com apiUrl e apiKey
804
+ // Usamos useMemo para garantir que a configuração ocorra ANTES dos efeitos dos componentes filhos
805
+ React$1.useMemo(() => {
763
806
  configure({
764
807
  apiUrl,
765
808
  apiKey
@@ -855,6 +898,13 @@ const useSession = () => useAuthStore(useShallow(s => ({
855
898
  // Loading States Hook
856
899
  const useAuthLoading = () => useAuthStore(s => s.loadingStates);
857
900
 
901
+ // Application Logo Hook
902
+ const useApplicationLogo = () => {
903
+ const applicationInfo = useAuthStore(s => s.applicationInfo);
904
+ // Retorna o logo da aplicação ou null (componentes usam fallback padrão)
905
+ return applicationInfo?.image || null;
906
+ };
907
+
858
908
  function ProtectedRoute({
859
909
  fallback = /*#__PURE__*/React.createElement("p", null, "\u231B Carregando..."),
860
910
  redirectTo = "/login"
@@ -890,7 +940,7 @@ function AuthCard({
890
940
  title,
891
941
  subtitle,
892
942
  logo,
893
- logoHeight = 32,
943
+ logoHeight = 28,
894
944
  width = 350,
895
945
  ...props
896
946
  }) {
@@ -925,7 +975,8 @@ var img = "data:image/webp;base64,iVBORw0KGgoAAAANSUhEUgAAAjwAAADICAYAAADskzu8AA
925
975
  */
926
976
  function SignInForm({
927
977
  // Configuração
928
- logo = img,
978
+ logo,
979
+ // Removido default, será calculado abaixo
929
980
  title = 'Entrar',
930
981
  subtitle = 'Acesse sua conta para continuar',
931
982
  // Features
@@ -951,6 +1002,10 @@ function SignInForm({
951
1002
  const loadingSignIn = useAuthStore(s => s.loadingStates.signIn);
952
1003
  const loadingMagicLink = useAuthStore(s => s.loadingStates.magicLink);
953
1004
  const loadingResetPassword = useAuthStore(s => s.loadingStates.resetPassword);
1005
+
1006
+ // Hook para buscar logo da aplicação
1007
+ const applicationLogo = useApplicationLogo();
1008
+ const finalLogo = logo || applicationLogo || img;
954
1009
  const form$1 = form.useForm({
955
1010
  initialValues: {
956
1011
  email: '',
@@ -995,7 +1050,7 @@ function SignInForm({
995
1050
  };
996
1051
  const isLoading = loadingSignIn || loadingMagicLink || loadingResetPassword;
997
1052
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
998
- logo: logo,
1053
+ logo: finalLogo,
999
1054
  title: title,
1000
1055
  subtitle: subtitle
1001
1056
  }, cardProps), /*#__PURE__*/React.createElement("form", {
@@ -1073,7 +1128,7 @@ function SignInForm({
1073
1128
  */
1074
1129
  function SignUpForm({
1075
1130
  // Configuração
1076
- logo = img,
1131
+ logo,
1077
1132
  title = 'Criar Conta',
1078
1133
  subtitle = 'Preencha os dados para se cadastrar',
1079
1134
  // Features
@@ -1092,6 +1147,10 @@ function SignUpForm({
1092
1147
  }) {
1093
1148
  const signUp = useAuthStore(s => s.signUp);
1094
1149
  const loading = useAuthStore(s => s.loadingStates.signUp);
1150
+
1151
+ // Hook para buscar logo da aplicação
1152
+ const applicationLogo = useApplicationLogo();
1153
+ const finalLogo = logo || applicationLogo || img;
1095
1154
  const form$1 = form.useForm({
1096
1155
  initialValues: {
1097
1156
  name: '',
@@ -1115,7 +1174,7 @@ function SignUpForm({
1115
1174
  }
1116
1175
  };
1117
1176
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1118
- logo: logo,
1177
+ logo: finalLogo,
1119
1178
  title: title,
1120
1179
  subtitle: subtitle
1121
1180
  }, cardProps), /*#__PURE__*/React.createElement("form", {
@@ -1192,7 +1251,7 @@ function SignUpForm({
1192
1251
  */
1193
1252
  function MagicLinkForm({
1194
1253
  // Configuração
1195
- logo = img,
1254
+ logo,
1196
1255
  title = 'Login sem Senha',
1197
1256
  subtitle = 'Receba um link de acesso no seu email',
1198
1257
  // Features
@@ -1208,6 +1267,10 @@ function MagicLinkForm({
1208
1267
  }) {
1209
1268
  const sendMagicLink = useAuthStore(s => s.sendMagicLink);
1210
1269
  const loading = useAuthStore(s => s.loadingStates.magicLink);
1270
+
1271
+ // Hook para buscar logo da aplicação
1272
+ const applicationLogo = useApplicationLogo();
1273
+ const finalLogo = logo || applicationLogo || img;
1211
1274
  const form$1 = form.useForm({
1212
1275
  initialValues: {
1213
1276
  email: ''
@@ -1225,7 +1288,7 @@ function MagicLinkForm({
1225
1288
  }
1226
1289
  };
1227
1290
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1228
- logo: logo,
1291
+ logo: finalLogo,
1229
1292
  title: title,
1230
1293
  subtitle: subtitle
1231
1294
  }, cardProps), /*#__PURE__*/React.createElement("form", {
@@ -1261,7 +1324,7 @@ function MagicLinkForm({
1261
1324
  */
1262
1325
  function MagicLinkVerify({
1263
1326
  // Configuração
1264
- logo = img,
1327
+ logo,
1265
1328
  // Token pode ser passado diretamente ou extraído da URL
1266
1329
  token: propToken,
1267
1330
  // Callbacks
@@ -1277,6 +1340,11 @@ function MagicLinkVerify({
1277
1340
  const [status, setStatus] = React$1.useState('verifying'); // verifying, success, error
1278
1341
  const [errorMessage, setErrorMessage] = React$1.useState('');
1279
1342
  const verifyMagicLink = useAuthStore(s => s.verifyMagicLink);
1343
+
1344
+ // Hook para buscar logo da aplicação
1345
+ const applicationLogo = useApplicationLogo();
1346
+ const finalLogo = logo || applicationLogo || img;
1347
+ const verifyingTokenRef = React$1.useRef(null);
1280
1348
  React$1.useEffect(() => {
1281
1349
  const verify = async () => {
1282
1350
  // Pega token da prop ou da URL
@@ -1291,6 +1359,10 @@ function MagicLinkVerify({
1291
1359
  onError?.(new Error('No token'));
1292
1360
  return;
1293
1361
  }
1362
+
1363
+ // Evita verificar o mesmo token duas vezes (React Strict Mode ou re-renders)
1364
+ if (verifyingTokenRef.current === token) return;
1365
+ verifyingTokenRef.current = token;
1294
1366
  try {
1295
1367
  const result = await verifyMagicLink(token);
1296
1368
  setStatus('success');
@@ -1307,9 +1379,10 @@ function MagicLinkVerify({
1307
1379
  }
1308
1380
  };
1309
1381
  verify();
1310
- }, [propToken, verifyMagicLink, onSuccess, onError, redirectTo, redirectDelay, labels]);
1382
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1383
+ }, [propToken, redirectTo, redirectDelay]);
1311
1384
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1312
- logo: logo
1385
+ logo: finalLogo
1313
1386
  }, cardProps), status === 'verifying' && /*#__PURE__*/React.createElement(core.Stack, {
1314
1387
  align: "center",
1315
1388
  gap: "sm"
@@ -1353,7 +1426,7 @@ function MagicLinkVerify({
1353
1426
  */
1354
1427
  function ForgotPasswordForm({
1355
1428
  // Configuração
1356
- logo = img,
1429
+ logo,
1357
1430
  title = 'Recuperar Senha',
1358
1431
  subtitle = 'Enviaremos um link para redefinir sua senha',
1359
1432
  // Features
@@ -1369,6 +1442,10 @@ function ForgotPasswordForm({
1369
1442
  }) {
1370
1443
  const forgotPassword = useAuthStore(s => s.forgotPassword);
1371
1444
  const loading = useAuthStore(s => s.loadingStates.resetPassword);
1445
+
1446
+ // Hook para buscar logo da aplicação
1447
+ const applicationLogo = useApplicationLogo();
1448
+ const finalLogo = logo || applicationLogo || img;
1372
1449
  const form$1 = form.useForm({
1373
1450
  initialValues: {
1374
1451
  email: ''
@@ -1386,7 +1463,7 @@ function ForgotPasswordForm({
1386
1463
  }
1387
1464
  };
1388
1465
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1389
- logo: logo,
1466
+ logo: finalLogo,
1390
1467
  title: title,
1391
1468
  subtitle: subtitle
1392
1469
  }, cardProps), /*#__PURE__*/React.createElement("form", {
@@ -1421,7 +1498,7 @@ function ForgotPasswordForm({
1421
1498
  */
1422
1499
  function ResetPasswordForm({
1423
1500
  // Configuração
1424
- logo = img,
1501
+ logo,
1425
1502
  title = 'Nova Senha',
1426
1503
  subtitle = 'Crie uma nova senha para sua conta',
1427
1504
  // Token pode ser passado diretamente ou extraído da URL
@@ -1440,6 +1517,10 @@ function ResetPasswordForm({
1440
1517
  const resetPassword = useAuthStore(s => s.resetPassword);
1441
1518
  const loading = useAuthStore(s => s.loadingStates.resetPassword);
1442
1519
 
1520
+ // Hook para buscar logo da aplicação
1521
+ const applicationLogo = useApplicationLogo();
1522
+ const finalLogo = logo || applicationLogo || img;
1523
+
1443
1524
  // Extrai token da URL se não foi passado como prop
1444
1525
  React$1.useEffect(() => {
1445
1526
  if (!propToken && typeof window !== 'undefined') {
@@ -1473,7 +1554,7 @@ function ResetPasswordForm({
1473
1554
  };
1474
1555
  if (success) {
1475
1556
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1476
- logo: logo
1557
+ logo: finalLogo
1477
1558
  }, cardProps), /*#__PURE__*/React.createElement(core.Stack, {
1478
1559
  align: "center",
1479
1560
  gap: "sm"
@@ -1494,7 +1575,7 @@ function ResetPasswordForm({
1494
1575
  }
1495
1576
  if (!token) {
1496
1577
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1497
- logo: logo
1578
+ logo: finalLogo
1498
1579
  }, cardProps), /*#__PURE__*/React.createElement(core.Stack, {
1499
1580
  align: "center",
1500
1581
  gap: "sm"
@@ -1508,7 +1589,7 @@ function ResetPasswordForm({
1508
1589
  }, labels.goToSignIn || 'Voltar para Login')));
1509
1590
  }
1510
1591
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1511
- logo: logo,
1592
+ logo: finalLogo,
1512
1593
  title: title,
1513
1594
  subtitle: subtitle
1514
1595
  }, cardProps), /*#__PURE__*/React.createElement("form", {
@@ -1544,7 +1625,7 @@ function ResetPasswordForm({
1544
1625
  */
1545
1626
  function VerifyEmailCard({
1546
1627
  // Configuração
1547
- logo = img,
1628
+ logo,
1548
1629
  // Token pode ser passado diretamente ou extraído da URL
1549
1630
  token: propToken,
1550
1631
  // Email para reenvio (opcional)
@@ -1562,9 +1643,17 @@ function VerifyEmailCard({
1562
1643
  }) {
1563
1644
  const [status, setStatus] = React$1.useState('verifying'); // verifying, success, error
1564
1645
  const [errorMessage, setErrorMessage] = React$1.useState('');
1646
+ const [tokenEmail, setTokenEmail] = React$1.useState(null); // Novo estado para email do token
1647
+
1565
1648
  const verifyEmail = useAuthStore(s => s.verifyEmail);
1566
1649
  const resendVerification = useAuthStore(s => s.resendVerification);
1567
1650
  const loadingResend = useAuthStore(s => s.loadingStates.resendVerification);
1651
+ const user = useAuthStore(s => s.user);
1652
+ const verifyingTokenRef = React$1.useRef(null);
1653
+
1654
+ // Hook para buscar logo da aplicação
1655
+ const applicationLogo = useApplicationLogo();
1656
+ const finalLogo = logo || applicationLogo || img;
1568
1657
  React$1.useEffect(() => {
1569
1658
  const verify = async () => {
1570
1659
  // Pega token da prop ou da URL
@@ -1574,11 +1663,25 @@ function VerifyEmailCard({
1574
1663
  token = urlParams.get('token');
1575
1664
  }
1576
1665
  if (!token) {
1577
- setStatus('error');
1578
- setErrorMessage(labels.noToken || 'Token não encontrado na URL');
1579
- onError?.(new Error('No token'));
1666
+ // Se já mostramos o erro de token faltando, não precisa fazer nada
1667
+ if (status === 'missing_token') return;
1668
+ setStatus('missing_token');
1580
1669
  return;
1581
1670
  }
1671
+
1672
+ // Tenta extrair email do token (mesmo expirado) para permitir reenvio
1673
+ try {
1674
+ const payload = decodeJWT(token);
1675
+ if (payload?.email) {
1676
+ setTokenEmail(payload.email);
1677
+ }
1678
+ } catch (e) {
1679
+ // ignore jwt error
1680
+ }
1681
+
1682
+ // Evita verificar o mesmo token duas vezes (React Strict Mode ou re-renders)
1683
+ if (verifyingTokenRef.current === token) return;
1684
+ verifyingTokenRef.current = token;
1582
1685
  try {
1583
1686
  const result = await verifyEmail(token);
1584
1687
  setStatus('success');
@@ -1589,24 +1692,28 @@ function VerifyEmailCard({
1589
1692
  }, redirectDelay);
1590
1693
  }
1591
1694
  } catch (error) {
1695
+ // Se der erro, permitimos tentar novamente apenas se o usuário recarregar
1696
+ // ou se implementarmos um botão de retry que limpe o ref
1592
1697
  setStatus('error');
1593
1698
  setErrorMessage(error.message || labels.verificationFailed || 'Verificação falhou');
1594
1699
  onError?.(error);
1595
1700
  }
1596
1701
  };
1597
1702
  verify();
1598
- }, [propToken, verifyEmail, onSuccess, onError, redirectTo, redirectDelay, labels]);
1703
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1704
+ }, [propToken, redirectTo, redirectDelay]);
1599
1705
  const handleResend = async () => {
1600
- if (!email) return;
1706
+ const targetEmail = email || user?.email || tokenEmail;
1707
+ if (!targetEmail) return;
1601
1708
  try {
1602
- await resendVerification(email);
1603
- onResent?.(email);
1709
+ await resendVerification(targetEmail);
1710
+ onResent?.(targetEmail);
1604
1711
  } catch (error) {
1605
1712
  onError?.(error);
1606
1713
  }
1607
1714
  };
1608
1715
  return /*#__PURE__*/React.createElement(AuthCard, _extends({
1609
- logo: logo
1716
+ logo: finalLogo
1610
1717
  }, cardProps), status === 'verifying' && /*#__PURE__*/React.createElement(core.Stack, {
1611
1718
  align: "center",
1612
1719
  gap: "sm"
@@ -1618,7 +1725,19 @@ function VerifyEmailCard({
1618
1725
  size: "sm",
1619
1726
  c: "dimmed",
1620
1727
  ta: "center"
1621
- }, labels.pleaseWait || 'Aguarde enquanto verificamos seu email...')), status === 'success' && /*#__PURE__*/React.createElement(core.Stack, {
1728
+ }, labels.pleaseWait || 'Aguarde enquanto verificamos seu email...')), status === 'missing_token' && /*#__PURE__*/React.createElement(core.Stack, {
1729
+ align: "center",
1730
+ gap: "sm"
1731
+ }, /*#__PURE__*/React.createElement(iconsReact.IconMail, {
1732
+ size: 48,
1733
+ color: "var(--mantine-color-blue-6)"
1734
+ }), /*#__PURE__*/React.createElement(core.Title, {
1735
+ order: 4
1736
+ }, labels.verification || 'Verificação de Email'), /*#__PURE__*/React.createElement(core.Text, {
1737
+ size: "sm",
1738
+ c: "dimmed",
1739
+ ta: "center"
1740
+ }, labels.missingToken || 'Para verificar seu conta, clique no link enviado para seu email.')), status === 'success' && /*#__PURE__*/React.createElement(core.Stack, {
1622
1741
  align: "center",
1623
1742
  gap: "sm"
1624
1743
  }, /*#__PURE__*/React.createElement(iconsReact.IconCheck, {
@@ -1642,7 +1761,7 @@ function VerifyEmailCard({
1642
1761
  size: "sm",
1643
1762
  c: "dimmed",
1644
1763
  ta: "center"
1645
- }, errorMessage || labels.invalidToken || 'O link é inválido ou expirou.'), email && /*#__PURE__*/React.createElement(core.Button, {
1764
+ }, errorMessage || labels.invalidToken || 'O link é inválido ou expirou.'), (email || user?.email || tokenEmail) && errorMessage?.toLowerCase().includes('expired') && /*#__PURE__*/React.createElement(core.Button, {
1646
1765
  variant: "light",
1647
1766
  leftSection: /*#__PURE__*/React.createElement(iconsReact.IconRefresh, {
1648
1767
  size: 16
@@ -1650,7 +1769,7 @@ function VerifyEmailCard({
1650
1769
  onClick: handleResend,
1651
1770
  loading: loadingResend,
1652
1771
  fullWidth: true
1653
- }, labels.resend || 'Reenviar Email de Verificação')));
1772
+ }, labels.resend || 'Solicitar novo link')));
1654
1773
  }
1655
1774
 
1656
1775
  exports.AuthCard = AuthCard;
@@ -1664,7 +1783,9 @@ exports.SignInForm = SignInForm;
1664
1783
  exports.SignUpForm = SignUpForm;
1665
1784
  exports.VerifyEmailCard = VerifyEmailCard;
1666
1785
  exports.configure = configure;
1786
+ exports.decodeJWT = decodeJWT;
1667
1787
  exports.forgotPassword = forgotPassword;
1788
+ exports.getApplicationInfo = getApplicationInfo;
1668
1789
  exports.getCurrentUser = getCurrentUser;
1669
1790
  exports.getSession = getSession;
1670
1791
  exports.isAuthenticated = isAuthenticated;