@riligar/auth-react 1.14.0 → 1.16.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
@@ -3,9 +3,9 @@ import { useMemo, useEffect, createContext, useState, useRef } from 'react';
3
3
  import { useShallow } from 'zustand/react/shallow';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { Navigate, Outlet, useNavigate } from 'react-router-dom';
6
- import { Modal, Stack, Text, Image, Title, Paper, TextInput, PasswordInput, Anchor, Button, Divider, Group, Center, Loader, Box, Avatar, Collapse, Card, Tooltip, ThemeIcon } from '@mantine/core';
6
+ import { Modal, Stack, Text, Image, Title, Paper, TextInput, PasswordInput, Anchor, Button, Divider, Group, Center, Loader, Box, Avatar, Collapse, Card, Tooltip, ThemeIcon, Badge } from '@mantine/core';
7
7
  import { useForm } from '@mantine/form';
8
- import { IconMail, IconLock, IconArrowRight, IconBrandGoogle, IconBrandGithub, IconUser, IconSend, IconCheck, IconX, IconRefresh, IconPhoto, IconTrash, IconPencil, IconShield, IconKey, IconUserCircle } from '@tabler/icons-react';
8
+ import { IconMail, IconLock, IconArrowRight, IconBrandGoogle, IconBrandGithub, IconUser, IconSend, IconCheck, IconX, IconRefresh, IconPhoto, IconTrash, IconPencil, IconShield, IconKey, IconDevices, IconDeviceMobile, IconLogout, IconUserCircle } from '@tabler/icons-react';
9
9
  import { notifications } from '@mantine/notifications';
10
10
 
11
11
  // Config - pode ser sobrescrita pelo AuthProvider
@@ -232,7 +232,7 @@ const verifyMagicLink = async token => {
232
232
  /*--- Password Reset -----------------------------*/
233
233
  const forgotPassword = async (email, redirectTo) => {
234
234
  const redirect = redirectTo || (typeof window !== 'undefined' ? `${window.location.origin}/auth/reset-password` : '/auth/reset-password');
235
- return await api('/auth/forget-password', {
235
+ return await api('/auth/request-password-reset', {
236
236
  method: 'POST',
237
237
  body: JSON.stringify({
238
238
  email,
@@ -270,6 +270,22 @@ const resendVerification = async (email, callbackURL) => {
270
270
  const getSession = async () => {
271
271
  return await api('/auth/session');
272
272
  };
273
+ const listSessions = async () => {
274
+ return await api('/auth/list-sessions');
275
+ };
276
+ const revokeSession = async id => {
277
+ return await api('/auth/revoke-session-by-id', {
278
+ method: 'POST',
279
+ body: JSON.stringify({
280
+ id
281
+ })
282
+ });
283
+ };
284
+ const revokeOtherSessions = async () => {
285
+ return await api('/auth/revoke-other-sessions', {
286
+ method: 'POST'
287
+ });
288
+ };
273
289
 
274
290
  /*--- Application Info ----------------------------*/
275
291
  const getApplicationInfo = async () => {
@@ -321,6 +337,9 @@ const useAuthStore = create((set, get) => ({
321
337
  user: null,
322
338
  loading: true,
323
339
  error: null,
340
+ // Session management
341
+ sessions: [],
342
+ currentSession: null,
324
343
  // Loading states granulares
325
344
  loadingStates: {
326
345
  signIn: false,
@@ -333,7 +352,9 @@ const useAuthStore = create((set, get) => ({
333
352
  resendVerification: false,
334
353
  updateProfile: false,
335
354
  changePassword: false,
336
- changeEmail: false
355
+ changeEmail: false,
356
+ listSessions: false,
357
+ revokeSession: false
337
358
  },
338
359
  // Application info (logo, nome, etc)
339
360
  applicationInfo: null,
@@ -376,9 +397,14 @@ const useAuthStore = create((set, get) => ({
376
397
  // Precisamos buscar a sessão no servidor.
377
398
  if (!user) {
378
399
  try {
379
- const session = await getSession();
380
- if (session?.user) {
381
- user = session.user;
400
+ const sessionData = await getSession();
401
+ if (sessionData?.user) {
402
+ user = sessionData.user;
403
+ }
404
+ if (sessionData?.session) {
405
+ set({
406
+ currentSession: sessionData.session
407
+ });
382
408
  }
383
409
  } catch (sessionError) {
384
410
  console.warn('[AuthStore] Failed to fetch session:', sessionError);
@@ -451,8 +477,13 @@ const useAuthStore = create((set, get) => ({
451
477
 
452
478
  // Se não encontrou no token (sessão opaca), busca do servidor
453
479
  if (!user) {
454
- const session = await getSession();
455
- if (session?.user) user = session.user;
480
+ const sessionData = await getSession();
481
+ if (sessionData?.user) user = sessionData.user;
482
+ if (sessionData?.session) {
483
+ set({
484
+ currentSession: sessionData.session
485
+ });
486
+ }
456
487
  }
457
488
  set({
458
489
  user,
@@ -634,11 +665,86 @@ const useAuthStore = create((set, get) => ({
634
665
  /* Session */
635
666
  getSession: async () => {
636
667
  try {
637
- return await getSession();
668
+ const sessionData = await getSession();
669
+ // Store current session for comparison (includes id)
670
+ if (sessionData?.session) {
671
+ set({
672
+ currentSession: sessionData.session
673
+ });
674
+ }
675
+ return sessionData;
676
+ } catch (err) {
677
+ set({
678
+ error: err
679
+ });
680
+ throw err;
681
+ }
682
+ },
683
+ listSessions: async () => {
684
+ const {
685
+ setLoading
686
+ } = get();
687
+ setLoading('listSessions', true);
688
+ set({
689
+ error: null
690
+ });
691
+ try {
692
+ const result = await listSessions();
693
+ set({
694
+ sessions: result || []
695
+ });
696
+ setLoading('listSessions', false);
697
+ return result;
698
+ } catch (err) {
699
+ set({
700
+ error: err,
701
+ sessions: []
702
+ });
703
+ setLoading('listSessions', false);
704
+ throw err;
705
+ }
706
+ },
707
+ revokeSession: async token => {
708
+ const {
709
+ setLoading,
710
+ listSessions
711
+ } = get();
712
+ setLoading('revokeSession', true);
713
+ set({
714
+ error: null
715
+ });
716
+ try {
717
+ await revokeSession(token);
718
+ // Refresh sessions list after revocation
719
+ await listSessions();
720
+ setLoading('revokeSession', false);
721
+ } catch (err) {
722
+ set({
723
+ error: err
724
+ });
725
+ setLoading('revokeSession', false);
726
+ throw err;
727
+ }
728
+ },
729
+ revokeOtherSessions: async () => {
730
+ const {
731
+ setLoading,
732
+ listSessions
733
+ } = get();
734
+ setLoading('revokeSession', true);
735
+ set({
736
+ error: null
737
+ });
738
+ try {
739
+ await revokeOtherSessions();
740
+ // Refresh sessions list after revocation
741
+ await listSessions();
742
+ setLoading('revokeSession', false);
638
743
  } catch (err) {
639
744
  set({
640
745
  error: err
641
746
  });
747
+ setLoading('revokeSession', false);
642
748
  throw err;
643
749
  }
644
750
  },
@@ -818,8 +924,24 @@ function AuthProvider({
818
924
  checkTokenValidity();
819
925
  }
820
926
  };
927
+
928
+ // Escuta evento de sessão revogada (quando o usuário revoga sua própria sessão)
929
+ const handleSessionRevoked = () => {
930
+ // Limpa o usuário do store
931
+ useAuthStore.setState({
932
+ user: null,
933
+ currentSession: null,
934
+ sessions: []
935
+ });
936
+ // Dispara evento de logout para sincronizar entre abas
937
+ localStorage.setItem('auth:logout', Date.now());
938
+ };
821
939
  window.addEventListener('storage', handleStorageChange);
822
- return () => window.removeEventListener('storage', handleStorageChange);
940
+ window.addEventListener('auth:session-revoked', handleSessionRevoked);
941
+ return () => {
942
+ window.removeEventListener('storage', handleStorageChange);
943
+ window.removeEventListener('auth:session-revoked', handleSessionRevoked);
944
+ };
823
945
  }, [checkTokenValidity]);
824
946
 
825
947
  // Verifica validade do token periodicamente
@@ -907,6 +1029,19 @@ const useUser = () => useAuthStore(useShallow(s => ({
907
1029
  // Alias deprecado para backwards compatibility
908
1030
  const useProfile = useUser;
909
1031
 
1032
+ // Sessions Management Hook
1033
+ const useSessions = () => useAuthStore(useShallow(s => ({
1034
+ currentSession: s.currentSession,
1035
+ sessions: s.sessions,
1036
+ getSession: s.getSession,
1037
+ listSessions: s.listSessions,
1038
+ revokeSession: s.revokeSession,
1039
+ revokeOtherSessions: s.revokeOtherSessions,
1040
+ loadingListSessions: s.loadingStates.listSessions,
1041
+ loadingRevokeSession: s.loadingStates.revokeSession,
1042
+ error: s.error
1043
+ })));
1044
+
910
1045
  // Application Logo Hook
911
1046
  const useApplicationLogo = () => {
912
1047
  const applicationInfo = useAuthStore(s => s.applicationInfo);
@@ -1936,6 +2071,7 @@ function UserProfile({
1936
2071
  showName = true,
1937
2072
  showEmail = true,
1938
2073
  showPassword = true,
2074
+ showSessions = true,
1939
2075
  // Customização
1940
2076
  labels = {},
1941
2077
  title = 'Account',
@@ -1963,6 +2099,111 @@ function UserProfile({
1963
2099
  loadingChangeEmail
1964
2100
  } = useProfile();
1965
2101
 
2102
+ // Hook para sessions
2103
+ const {
2104
+ currentSession,
2105
+ sessions,
2106
+ listSessions,
2107
+ getSession,
2108
+ revokeSession,
2109
+ revokeOtherSessions,
2110
+ loadingListSessions,
2111
+ loadingRevokeSession
2112
+ } = useSessions();
2113
+
2114
+ // Load sessions when opened (modal) or component mounts (card), and when sessions section is opened
2115
+ useEffect(() => {
2116
+ if (showSessions && (variant === 'card' || opened)) {
2117
+ // Fetch current session first to get the session ID, then list all sessions
2118
+ getSession().catch(err => console.warn('Failed to get current session:', err));
2119
+ listSessions().catch(err => console.warn('Failed to load sessions:', err));
2120
+ }
2121
+ }, [opened, showSessions, variant]);
2122
+
2123
+ // Helper to parse user agent string
2124
+ const parseUserAgent = ua => {
2125
+ if (!ua) return {
2126
+ browser: 'Unknown Browser',
2127
+ os: 'Unknown OS'
2128
+ };
2129
+ let browser = 'Unknown Browser';
2130
+ let os = 'Unknown OS';
2131
+
2132
+ // Detect browser
2133
+ if (ua.includes('Chrome') && !ua.includes('Edg')) browser = 'Chrome';else if (ua.includes('Firefox')) browser = 'Firefox';else if (ua.includes('Safari') && !ua.includes('Chrome')) browser = 'Safari';else if (ua.includes('Edg')) browser = 'Edge';else if (ua.includes('Opera') || ua.includes('OPR')) browser = 'Opera';
2134
+
2135
+ // Detect OS
2136
+ if (ua.includes('Windows')) os = 'Windows';else if (ua.includes('Mac OS')) os = 'macOS';else if (ua.includes('Linux')) os = 'Linux';else if (ua.includes('Android')) os = 'Android';else if (ua.includes('iPhone') || ua.includes('iPad')) os = 'iOS';
2137
+ return {
2138
+ browser,
2139
+ os
2140
+ };
2141
+ };
2142
+
2143
+ // Session handlers
2144
+ const handleRevokeSession = async sessionId => {
2145
+ // Check if revoking current session
2146
+ const isCurrentSession = sessionId === currentSession?.id || sessions.length === 1;
2147
+
2148
+ // If revoking current session, handle logout immediately after revocation
2149
+ if (isCurrentSession) {
2150
+ try {
2151
+ await revokeSession(sessionId);
2152
+ } catch (error) {
2153
+ // Even if it fails, we're revoking our own session, so just logout
2154
+ // The server already revoked our session
2155
+ }
2156
+ // Clear auth state and redirect
2157
+ localStorage.removeItem('auth:token');
2158
+ window.dispatchEvent(new CustomEvent('auth:session-revoked'));
2159
+ if (variant === 'modal') onClose?.();
2160
+ return;
2161
+ }
2162
+
2163
+ // Revoking another session
2164
+ try {
2165
+ await revokeSession(sessionId);
2166
+ notifications.show({
2167
+ title: labels.successTitle || 'Sucesso',
2168
+ message: labels.sessionRevoked || 'Sessão encerrada com sucesso',
2169
+ color: 'green'
2170
+ });
2171
+ } catch (error) {
2172
+ // Check for 401 error (our SDK uses error.res, axios uses error.response)
2173
+ const status = error.res?.status || error.response?.status;
2174
+ if (status === 401) {
2175
+ // This means our session was revoked, not the target one - do logout
2176
+ localStorage.removeItem('auth:token');
2177
+ window.dispatchEvent(new CustomEvent('auth:session-revoked'));
2178
+ if (variant === 'modal') onClose?.();
2179
+ return;
2180
+ }
2181
+ notifications.show({
2182
+ title: labels.errorTitle || 'Erro',
2183
+ message: error.message || labels.sessionRevokeFailed || 'Falha ao encerrar sessão',
2184
+ color: 'red'
2185
+ });
2186
+ onError?.(error);
2187
+ }
2188
+ };
2189
+ const handleRevokeOtherSessions = async () => {
2190
+ try {
2191
+ await revokeOtherSessions();
2192
+ notifications.show({
2193
+ title: labels.successTitle || 'Sucesso',
2194
+ message: labels.otherSessionsRevoked || 'Todas as outras sessões foram encerradas',
2195
+ color: 'green'
2196
+ });
2197
+ } catch (error) {
2198
+ notifications.show({
2199
+ title: labels.errorTitle || 'Erro',
2200
+ message: error.message || labels.otherSessionsRevokeFailed || 'Falha ao encerrar sessões',
2201
+ color: 'red'
2202
+ });
2203
+ onError?.(error);
2204
+ }
2205
+ };
2206
+
1966
2207
  // Password form
1967
2208
  const passwordForm = useForm({
1968
2209
  initialValues: {
@@ -2494,7 +2735,7 @@ function UserProfile({
2494
2735
  })]
2495
2736
  })]
2496
2737
  })]
2497
- }), showPassword && /*#__PURE__*/jsxs(Box, {
2738
+ }), (showPassword || showSessions) && /*#__PURE__*/jsxs(Box, {
2498
2739
  mb: "md",
2499
2740
  children: [/*#__PURE__*/jsx(SectionHeader, {
2500
2741
  icon: IconShield,
@@ -2502,69 +2743,196 @@ function UserProfile({
2502
2743
  description: labels.securityDescription || 'Protect your account'
2503
2744
  }), /*#__PURE__*/jsxs(Stack, {
2504
2745
  gap: "sm",
2505
- children: [/*#__PURE__*/jsx(SettingRow, {
2506
- label: labels.password || 'Password',
2507
- action: labels.change || 'Change',
2508
- actionLabel: labels.changePassword || 'Change your password',
2509
- onClick: () => handleToggleSection('password'),
2510
- expanded: editingSection === 'password',
2511
- children: /*#__PURE__*/jsxs(Group, {
2512
- gap: "xs",
2513
- children: [/*#__PURE__*/jsx(IconKey, {
2514
- size: 16,
2515
- color: "var(--mantine-color-dimmed)"
2516
- }), /*#__PURE__*/jsx(Text, {
2517
- size: "sm",
2518
- c: "dimmed",
2519
- children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"
2520
- })]
2521
- })
2522
- }), /*#__PURE__*/jsx(Collapse, {
2523
- in: editingSection === 'password',
2524
- children: /*#__PURE__*/jsx(Card, {
2525
- p: "md",
2526
- withBorder: true,
2527
- children: /*#__PURE__*/jsx("form", {
2528
- onSubmit: passwordForm.onSubmit(handleChangePassword),
2529
- children: /*#__PURE__*/jsxs(Stack, {
2530
- gap: "sm",
2531
- children: [/*#__PURE__*/jsx(PasswordInput, {
2532
- label: labels.currentPassword || 'Senha Atual',
2533
- placeholder: labels.currentPasswordPlaceholder || 'Digite sua senha atual',
2534
- ...passwordForm.getInputProps('currentPassword')
2535
- }), /*#__PURE__*/jsx(PasswordInput, {
2536
- label: labels.newPassword || 'Nova Senha',
2537
- placeholder: labels.newPasswordPlaceholder || 'Digite a nova senha',
2538
- description: labels.passwordDescription || 'Mínimo 8 caracteres',
2539
- ...passwordForm.getInputProps('newPassword')
2540
- }), /*#__PURE__*/jsx(PasswordInput, {
2541
- label: labels.confirmPassword || 'Confirmar Nova Senha',
2542
- placeholder: labels.confirmPasswordPlaceholder || 'Confirme a nova senha',
2543
- ...passwordForm.getInputProps('confirmPassword')
2544
- }), /*#__PURE__*/jsxs(Group, {
2545
- justify: "flex-end",
2546
- gap: "xs",
2547
- children: [/*#__PURE__*/jsx(Button, {
2548
- variant: "default",
2549
- size: "xs",
2550
- onClick: () => handleToggleSection('password'),
2551
- leftSection: /*#__PURE__*/jsx(IconX, {
2552
- size: 14
2553
- }),
2554
- children: labels.cancel || 'Cancelar'
2555
- }), /*#__PURE__*/jsx(Button, {
2556
- type: "submit",
2557
- size: "xs",
2558
- loading: loadingChangePassword,
2559
- leftSection: /*#__PURE__*/jsx(IconCheck, {
2560
- size: 14
2561
- }),
2562
- children: labels.save || 'Salvar'
2746
+ children: [showPassword && /*#__PURE__*/jsxs(Fragment, {
2747
+ children: [/*#__PURE__*/jsx(SettingRow, {
2748
+ label: labels.password || 'Password',
2749
+ action: labels.change || 'Change',
2750
+ actionLabel: labels.changePassword || 'Change your password',
2751
+ onClick: () => handleToggleSection('password'),
2752
+ expanded: editingSection === 'password',
2753
+ children: /*#__PURE__*/jsxs(Group, {
2754
+ gap: "xs",
2755
+ children: [/*#__PURE__*/jsx(IconKey, {
2756
+ size: 16,
2757
+ color: "var(--mantine-color-dimmed)"
2758
+ }), /*#__PURE__*/jsx(Text, {
2759
+ size: "sm",
2760
+ c: "dimmed",
2761
+ children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"
2762
+ })]
2763
+ })
2764
+ }), /*#__PURE__*/jsx(Collapse, {
2765
+ in: editingSection === 'password',
2766
+ children: /*#__PURE__*/jsx(Card, {
2767
+ p: "md",
2768
+ withBorder: true,
2769
+ children: /*#__PURE__*/jsx("form", {
2770
+ onSubmit: passwordForm.onSubmit(handleChangePassword),
2771
+ children: /*#__PURE__*/jsxs(Stack, {
2772
+ gap: "sm",
2773
+ children: [/*#__PURE__*/jsx(PasswordInput, {
2774
+ label: labels.currentPassword || 'Senha Atual',
2775
+ placeholder: labels.currentPasswordPlaceholder || 'Digite sua senha atual',
2776
+ ...passwordForm.getInputProps('currentPassword')
2777
+ }), /*#__PURE__*/jsx(PasswordInput, {
2778
+ label: labels.newPassword || 'Nova Senha',
2779
+ placeholder: labels.newPasswordPlaceholder || 'Digite a nova senha',
2780
+ description: labels.passwordDescription || 'Mínimo 8 caracteres',
2781
+ ...passwordForm.getInputProps('newPassword')
2782
+ }), /*#__PURE__*/jsx(PasswordInput, {
2783
+ label: labels.confirmPassword || 'Confirmar Nova Senha',
2784
+ placeholder: labels.confirmPasswordPlaceholder || 'Confirme a nova senha',
2785
+ ...passwordForm.getInputProps('confirmPassword')
2786
+ }), /*#__PURE__*/jsxs(Group, {
2787
+ justify: "flex-end",
2788
+ gap: "xs",
2789
+ children: [/*#__PURE__*/jsx(Button, {
2790
+ variant: "default",
2791
+ size: "xs",
2792
+ onClick: () => handleToggleSection('password'),
2793
+ leftSection: /*#__PURE__*/jsx(IconX, {
2794
+ size: 14
2795
+ }),
2796
+ children: labels.cancel || 'Cancelar'
2797
+ }), /*#__PURE__*/jsx(Button, {
2798
+ type: "submit",
2799
+ size: "xs",
2800
+ loading: loadingChangePassword,
2801
+ leftSection: /*#__PURE__*/jsx(IconCheck, {
2802
+ size: 14
2803
+ }),
2804
+ children: labels.save || 'Salvar'
2805
+ })]
2563
2806
  })]
2564
- })]
2807
+ })
2565
2808
  })
2566
2809
  })
2567
- })
2810
+ })]
2811
+ }), showSessions && /*#__PURE__*/jsxs(Fragment, {
2812
+ children: [/*#__PURE__*/jsx(SettingRow, {
2813
+ label: labels.sessions || 'Sessions',
2814
+ action: editingSection === 'sessions' ? labels.close || 'Close' : labels.manage || 'Manage',
2815
+ actionLabel: labels.manageSessions || 'Manage your active sessions',
2816
+ onClick: () => handleToggleSection('sessions'),
2817
+ expanded: editingSection === 'sessions',
2818
+ children: /*#__PURE__*/jsxs(Group, {
2819
+ gap: "xs",
2820
+ children: [/*#__PURE__*/jsx(IconDevices, {
2821
+ size: 16,
2822
+ color: "var(--mantine-color-dimmed)"
2823
+ }), /*#__PURE__*/jsx(Text, {
2824
+ size: "sm",
2825
+ c: "dimmed",
2826
+ children: sessions.length > 0 ? `${sessions.length} active session${sessions.length > 1 ? 's' : ''}` : loadingListSessions ? 'Loading...' : 'No sessions'
2827
+ })]
2828
+ })
2829
+ }), /*#__PURE__*/jsx(Collapse, {
2830
+ in: editingSection === 'sessions',
2831
+ children: /*#__PURE__*/jsx(Card, {
2832
+ p: "md",
2833
+ withBorder: true,
2834
+ children: /*#__PURE__*/jsx(Stack, {
2835
+ gap: "md",
2836
+ children: loadingListSessions ? /*#__PURE__*/jsx(Text, {
2837
+ size: "sm",
2838
+ c: "dimmed",
2839
+ ta: "center",
2840
+ children: labels.loadingSessions || 'Carregando sessões...'
2841
+ }) : sessions.length === 0 ? /*#__PURE__*/jsx(Text, {
2842
+ size: "sm",
2843
+ c: "dimmed",
2844
+ ta: "center",
2845
+ children: labels.noSessionsFound || 'Nenhuma sessão encontrada'
2846
+ }) : /*#__PURE__*/jsxs(Fragment, {
2847
+ children: [sessions.map(sessionItem => {
2848
+ const isCurrentSession = sessionItem.id === currentSession?.id;
2849
+ const deviceInfo = parseUserAgent(sessionItem.userAgent);
2850
+ const createdDate = new Date(sessionItem.createdAt);
2851
+ return /*#__PURE__*/jsx(Card, {
2852
+ p: "sm",
2853
+ style: {
2854
+ borderColor: isCurrentSession ? 'var(--mantine-color-blue-5)' : undefined,
2855
+ backgroundColor: isCurrentSession ? 'var(--mantine-color-blue-light)' : undefined
2856
+ },
2857
+ children: /*#__PURE__*/jsxs(Group, {
2858
+ justify: "space-between",
2859
+ wrap: "nowrap",
2860
+ children: [/*#__PURE__*/jsxs(Group, {
2861
+ gap: "sm",
2862
+ wrap: "nowrap",
2863
+ style: {
2864
+ flex: 1
2865
+ },
2866
+ children: [/*#__PURE__*/jsx(ThemeIcon, {
2867
+ size: 36,
2868
+ variant: "light",
2869
+ color: isCurrentSession ? 'blue' : 'gray',
2870
+ children: /*#__PURE__*/jsx(IconDeviceMobile, {
2871
+ size: 18
2872
+ })
2873
+ }), /*#__PURE__*/jsxs(Box, {
2874
+ style: {
2875
+ flex: 1
2876
+ },
2877
+ children: [/*#__PURE__*/jsxs(Group, {
2878
+ gap: "xs",
2879
+ children: [/*#__PURE__*/jsx(Text, {
2880
+ size: "sm",
2881
+ fw: 500,
2882
+ children: deviceInfo.browser
2883
+ }), isCurrentSession && /*#__PURE__*/jsx(Badge, {
2884
+ size: "xs",
2885
+ variant: "filled",
2886
+ color: "blue",
2887
+ children: labels.thisDevice || 'Este dispositivo'
2888
+ })]
2889
+ }), /*#__PURE__*/jsxs(Text, {
2890
+ size: "xs",
2891
+ c: "dimmed",
2892
+ children: [deviceInfo.os, " \u2022 ", sessionItem.ipAddress || labels.unknownIP || 'IP desconhecido']
2893
+ }), /*#__PURE__*/jsxs(Text, {
2894
+ size: "xs",
2895
+ c: "dimmed",
2896
+ children: [labels.createdAt || 'Criada em', " ", createdDate.toLocaleDateString('pt-BR'), " ", labels.at || 'às', ' ', createdDate.toLocaleTimeString('pt-BR', {
2897
+ hour: '2-digit',
2898
+ minute: '2-digit'
2899
+ })]
2900
+ })]
2901
+ })]
2902
+ }), /*#__PURE__*/jsx(Tooltip, {
2903
+ label: isCurrentSession ? labels.signOutAndEnd || 'Encerrar e sair' : labels.endSession || 'Encerrar sessão',
2904
+ children: /*#__PURE__*/jsx(Button, {
2905
+ variant: "light",
2906
+ color: "red",
2907
+ size: "xs",
2908
+ onClick: () => handleRevokeSession(sessionItem.id),
2909
+ loading: loadingRevokeSession,
2910
+ leftSection: /*#__PURE__*/jsx(IconLogout, {
2911
+ size: 14
2912
+ }),
2913
+ children: labels.end || 'Encerrar'
2914
+ })
2915
+ })]
2916
+ })
2917
+ }, sessionItem.id);
2918
+ }), sessions.length > 1 && /*#__PURE__*/jsx(Group, {
2919
+ justify: "flex-end",
2920
+ children: /*#__PURE__*/jsx(Button, {
2921
+ variant: "light",
2922
+ color: "red",
2923
+ size: "xs",
2924
+ onClick: handleRevokeOtherSessions,
2925
+ loading: loadingRevokeSession,
2926
+ leftSection: /*#__PURE__*/jsx(IconLogout, {
2927
+ size: 14
2928
+ }),
2929
+ children: labels.endOtherSessions || 'Encerrar todas as outras sessões'
2930
+ })
2931
+ })]
2932
+ })
2933
+ })
2934
+ })
2935
+ })]
2568
2936
  })]
2569
2937
  })]
2570
2938
  })]
@@ -2748,5 +3116,5 @@ function SignOutButton({
2748
3116
  });
2749
3117
  }
2750
3118
 
2751
- export { UserProfile as AccountModal, AuthCard, AuthLoaded, AuthLoading, AuthProvider, ForgotPassword, ForgotPassword as ForgotPasswordForm, MagicLink, MagicLinkCallback, MagicLink as MagicLinkForm, MagicLinkCallback as MagicLinkVerify, Protect, Protect as ProtectedRoute, ResetPassword, ResetPassword as ResetPasswordForm, SignIn, SignInButton, SignIn as SignInForm, SignOutButton, SignUp, SignUpButton, SignUp as SignUpForm, SignedIn, SignedOut, UserProfile, VerifyEmail, VerifyEmail as VerifyEmailCard, changeEmail, changePassword, configure, decodeJWT, forgotPassword, getApplicationInfo, getCurrentUser, getSession, isAuthenticated, refreshToken, resendVerification, resetPassword, sendMagicLink, signIn, signOut, signUp, socialRedirect, updateProfile, useApplicationLogo, useAuth, useAuthLoading, useAuthStore, useCheckToken, useEmailVerification, useMagicLink, usePasswordReset, useUser as useProfile, useSession, useSignIn, useSignOut, useSignUp, useUser, verifyEmail, verifyMagicLink };
3119
+ export { UserProfile as AccountModal, AuthCard, AuthLoaded, AuthLoading, AuthProvider, ForgotPassword, ForgotPassword as ForgotPasswordForm, MagicLink, MagicLinkCallback, MagicLink as MagicLinkForm, MagicLinkCallback as MagicLinkVerify, Protect, Protect as ProtectedRoute, ResetPassword, ResetPassword as ResetPasswordForm, SignIn, SignInButton, SignIn as SignInForm, SignOutButton, SignUp, SignUpButton, SignUp as SignUpForm, SignedIn, SignedOut, UserProfile, VerifyEmail, VerifyEmail as VerifyEmailCard, changeEmail, changePassword, configure, decodeJWT, forgotPassword, getApplicationInfo, getCurrentUser, getSession, isAuthenticated, listSessions, refreshToken, resendVerification, resetPassword, revokeOtherSessions, revokeSession, sendMagicLink, signIn, signOut, signUp, socialRedirect, updateProfile, useApplicationLogo, useAuth, useAuthLoading, useAuthStore, useCheckToken, useEmailVerification, useMagicLink, usePasswordReset, useUser as useProfile, useSession, useSessions, useSignIn, useSignOut, useSignUp, useUser, verifyEmail, verifyMagicLink };
2752
3120
  //# sourceMappingURL=index.esm.js.map