@riligar/auth-react 1.15.0 → 1.17.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
@@ -22,14 +22,17 @@ const getEnvVar = key => {
22
22
  };
23
23
  let API_BASE = (typeof window !== 'undefined' && window?.location ? getEnvVar('VITE_API_BASE') : null) || 'http://localhost:3000';
24
24
  let API_KEY = null;
25
+ let INTERNAL_MODE = false; // Modo interno: não exige API Key (para aplicações same-domain)
25
26
 
26
- // Permite configurar a API base e key externamente (chamado pelo AuthProvider)
27
+ // Permite configurar a API base, key e modo interno externamente (chamado pelo AuthProvider)
27
28
  function configure({
28
29
  apiUrl,
29
- apiKey
30
+ apiKey,
31
+ internal = false
30
32
  }) {
31
33
  if (apiUrl) API_BASE = apiUrl.replace(/\/$/, ''); // Remove trailing slash
32
34
  if (apiKey) API_KEY = apiKey;
35
+ INTERNAL_MODE = internal;
33
36
  }
34
37
 
35
38
  // helper fetch pré-configurado
@@ -51,8 +54,8 @@ async function api(route, opts = {}) {
51
54
  headers.Authorization = `Bearer ${token}`;
52
55
  }
53
56
 
54
- // Adiciona API Key se configurada
55
- if (API_KEY) {
57
+ // Adiciona API Key se configurada e não estiver em modo interno
58
+ if (API_KEY && !INTERNAL_MODE) {
56
59
  headers['X-API-Key'] = API_KEY;
57
60
  }
58
61
  const res = await fetch(url, {
@@ -273,11 +276,11 @@ const getSession = async () => {
273
276
  const listSessions = async () => {
274
277
  return await api('/auth/list-sessions');
275
278
  };
276
- const revokeSession = async token => {
277
- return await api('/auth/revoke-session', {
279
+ const revokeSession = async id => {
280
+ return await api('/auth/revoke-session-by-id', {
278
281
  method: 'POST',
279
282
  body: JSON.stringify({
280
- token
283
+ id
281
284
  })
282
285
  });
283
286
  };
@@ -339,7 +342,7 @@ const useAuthStore = create((set, get) => ({
339
342
  error: null,
340
343
  // Session management
341
344
  sessions: [],
342
- currentSessionToken: null,
345
+ currentSession: null,
343
346
  // Loading states granulares
344
347
  loadingStates: {
345
348
  signIn: false,
@@ -397,9 +400,14 @@ const useAuthStore = create((set, get) => ({
397
400
  // Precisamos buscar a sessão no servidor.
398
401
  if (!user) {
399
402
  try {
400
- const session = await getSession();
401
- if (session?.user) {
402
- user = session.user;
403
+ const sessionData = await getSession();
404
+ if (sessionData?.user) {
405
+ user = sessionData.user;
406
+ }
407
+ if (sessionData?.session) {
408
+ set({
409
+ currentSession: sessionData.session
410
+ });
403
411
  }
404
412
  } catch (sessionError) {
405
413
  console.warn('[AuthStore] Failed to fetch session:', sessionError);
@@ -472,8 +480,13 @@ const useAuthStore = create((set, get) => ({
472
480
 
473
481
  // Se não encontrou no token (sessão opaca), busca do servidor
474
482
  if (!user) {
475
- const session = await getSession();
476
- if (session?.user) user = session.user;
483
+ const sessionData = await getSession();
484
+ if (sessionData?.user) user = sessionData.user;
485
+ if (sessionData?.session) {
486
+ set({
487
+ currentSession: sessionData.session
488
+ });
489
+ }
477
490
  }
478
491
  set({
479
492
  user,
@@ -655,14 +668,14 @@ const useAuthStore = create((set, get) => ({
655
668
  /* Session */
656
669
  getSession: async () => {
657
670
  try {
658
- const session = await getSession();
659
- // Store current session token for comparison
660
- if (session?.session?.token) {
671
+ const sessionData = await getSession();
672
+ // Store current session for comparison (includes id)
673
+ if (sessionData?.session) {
661
674
  set({
662
- currentSessionToken: session.session.token
675
+ currentSession: sessionData.session
663
676
  });
664
677
  }
665
- return session;
678
+ return sessionData;
666
679
  } catch (err) {
667
680
  set({
668
681
  error: err
@@ -874,11 +887,14 @@ function AuthProvider({
874
887
  apiUrl,
875
888
  // URL base da API (OBRIGATÓRIA)
876
889
  apiKey,
877
- // API Key para header X-API-Key (OBRIGATÓRIA)
890
+ // API Key para header X-API-Key (obrigatória exceto em modo internal)
891
+ internal = false,
892
+ // Modo interno: não exige API Key (para aplicações same-domain como dashboard)
878
893
  onError // Callback de erro global
879
894
  }) {
880
895
  // Validação de props obrigatórias
881
- if (!apiKey) {
896
+ // apiKey só é obrigatória se não estiver em modo internal
897
+ if (!internal && !apiKey) {
882
898
  throw new Error('[@riligar/auth-react] apiKey é obrigatória no AuthProvider. ' + 'Obtenha sua API Key no dashboard em https://dashboard.myauth.click');
883
899
  }
884
900
  if (!apiUrl) {
@@ -888,15 +904,15 @@ function AuthProvider({
888
904
  const startRefresh = useAuthStore(s => s.startRefresh);
889
905
  const checkTokenValidity = useAuthStore(s => s.checkTokenValidity);
890
906
 
891
- // Configura SDK com apiUrl e apiKey
892
- // Configura SDK com apiUrl e apiKey
907
+ // Configura SDK com apiUrl, apiKey e modo interno
893
908
  // Usamos useMemo para garantir que a configuração ocorra ANTES dos efeitos dos componentes filhos
894
909
  useMemo(() => {
895
910
  configure({
896
911
  apiUrl,
897
- apiKey
912
+ apiKey,
913
+ internal
898
914
  });
899
- }, [apiUrl, apiKey]);
915
+ }, [apiUrl, apiKey, internal]);
900
916
  useEffect(() => {
901
917
  init();
902
918
  startRefresh();
@@ -914,8 +930,24 @@ function AuthProvider({
914
930
  checkTokenValidity();
915
931
  }
916
932
  };
933
+
934
+ // Escuta evento de sessão revogada (quando o usuário revoga sua própria sessão)
935
+ const handleSessionRevoked = () => {
936
+ // Limpa o usuário do store
937
+ useAuthStore.setState({
938
+ user: null,
939
+ currentSession: null,
940
+ sessions: []
941
+ });
942
+ // Dispara evento de logout para sincronizar entre abas
943
+ localStorage.setItem('auth:logout', Date.now());
944
+ };
917
945
  window.addEventListener('storage', handleStorageChange);
918
- return () => window.removeEventListener('storage', handleStorageChange);
946
+ window.addEventListener('auth:session-revoked', handleSessionRevoked);
947
+ return () => {
948
+ window.removeEventListener('storage', handleStorageChange);
949
+ window.removeEventListener('auth:session-revoked', handleSessionRevoked);
950
+ };
919
951
  }, [checkTokenValidity]);
920
952
 
921
953
  // Verifica validade do token periodicamente
@@ -1003,6 +1035,19 @@ const useUser = () => useAuthStore(useShallow(s => ({
1003
1035
  // Alias deprecado para backwards compatibility
1004
1036
  const useProfile = useUser;
1005
1037
 
1038
+ // Sessions Management Hook
1039
+ const useSessions = () => useAuthStore(useShallow(s => ({
1040
+ currentSession: s.currentSession,
1041
+ sessions: s.sessions,
1042
+ getSession: s.getSession,
1043
+ listSessions: s.listSessions,
1044
+ revokeSession: s.revokeSession,
1045
+ revokeOtherSessions: s.revokeOtherSessions,
1046
+ loadingListSessions: s.loadingStates.listSessions,
1047
+ loadingRevokeSession: s.loadingStates.revokeSession,
1048
+ error: s.error
1049
+ })));
1050
+
1006
1051
  // Application Logo Hook
1007
1052
  const useApplicationLogo = () => {
1008
1053
  const applicationInfo = useAuthStore(s => s.applicationInfo);
@@ -2032,6 +2077,7 @@ function UserProfile({
2032
2077
  showName = true,
2033
2078
  showEmail = true,
2034
2079
  showPassword = true,
2080
+ showSessions = true,
2035
2081
  // Customização
2036
2082
  labels = {},
2037
2083
  title = 'Account',
@@ -2059,6 +2105,111 @@ function UserProfile({
2059
2105
  loadingChangeEmail
2060
2106
  } = useProfile();
2061
2107
 
2108
+ // Hook para sessions
2109
+ const {
2110
+ currentSession,
2111
+ sessions,
2112
+ listSessions,
2113
+ getSession,
2114
+ revokeSession,
2115
+ revokeOtherSessions,
2116
+ loadingListSessions,
2117
+ loadingRevokeSession
2118
+ } = useSessions();
2119
+
2120
+ // Load sessions when opened (modal) or component mounts (card), and when sessions section is opened
2121
+ useEffect(() => {
2122
+ if (showSessions && (variant === 'card' || opened)) {
2123
+ // Fetch current session first to get the session ID, then list all sessions
2124
+ getSession().catch(err => console.warn('Failed to get current session:', err));
2125
+ listSessions().catch(err => console.warn('Failed to load sessions:', err));
2126
+ }
2127
+ }, [opened, showSessions, variant]);
2128
+
2129
+ // Helper to parse user agent string
2130
+ const parseUserAgent = ua => {
2131
+ if (!ua) return {
2132
+ browser: 'Unknown Browser',
2133
+ os: 'Unknown OS'
2134
+ };
2135
+ let browser = 'Unknown Browser';
2136
+ let os = 'Unknown OS';
2137
+
2138
+ // Detect browser
2139
+ 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';
2140
+
2141
+ // Detect OS
2142
+ 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';
2143
+ return {
2144
+ browser,
2145
+ os
2146
+ };
2147
+ };
2148
+
2149
+ // Session handlers
2150
+ const handleRevokeSession = async sessionId => {
2151
+ // Check if revoking current session
2152
+ const isCurrentSession = sessionId === currentSession?.id || sessions.length === 1;
2153
+
2154
+ // If revoking current session, handle logout immediately after revocation
2155
+ if (isCurrentSession) {
2156
+ try {
2157
+ await revokeSession(sessionId);
2158
+ } catch (error) {
2159
+ // Even if it fails, we're revoking our own session, so just logout
2160
+ // The server already revoked our session
2161
+ }
2162
+ // Clear auth state and redirect
2163
+ localStorage.removeItem('auth:token');
2164
+ window.dispatchEvent(new CustomEvent('auth:session-revoked'));
2165
+ if (variant === 'modal') onClose?.();
2166
+ return;
2167
+ }
2168
+
2169
+ // Revoking another session
2170
+ try {
2171
+ await revokeSession(sessionId);
2172
+ notifications.show({
2173
+ title: labels.successTitle || 'Sucesso',
2174
+ message: labels.sessionRevoked || 'Sessão encerrada com sucesso',
2175
+ color: 'green'
2176
+ });
2177
+ } catch (error) {
2178
+ // Check for 401 error (our SDK uses error.res, axios uses error.response)
2179
+ const status = error.res?.status || error.response?.status;
2180
+ if (status === 401) {
2181
+ // This means our session was revoked, not the target one - do logout
2182
+ localStorage.removeItem('auth:token');
2183
+ window.dispatchEvent(new CustomEvent('auth:session-revoked'));
2184
+ if (variant === 'modal') onClose?.();
2185
+ return;
2186
+ }
2187
+ notifications.show({
2188
+ title: labels.errorTitle || 'Erro',
2189
+ message: error.message || labels.sessionRevokeFailed || 'Falha ao encerrar sessão',
2190
+ color: 'red'
2191
+ });
2192
+ onError?.(error);
2193
+ }
2194
+ };
2195
+ const handleRevokeOtherSessions = async () => {
2196
+ try {
2197
+ await revokeOtherSessions();
2198
+ notifications.show({
2199
+ title: labels.successTitle || 'Sucesso',
2200
+ message: labels.otherSessionsRevoked || 'Todas as outras sessões foram encerradas',
2201
+ color: 'green'
2202
+ });
2203
+ } catch (error) {
2204
+ notifications.show({
2205
+ title: labels.errorTitle || 'Erro',
2206
+ message: error.message || labels.otherSessionsRevokeFailed || 'Falha ao encerrar sessões',
2207
+ color: 'red'
2208
+ });
2209
+ onError?.(error);
2210
+ }
2211
+ };
2212
+
2062
2213
  // Password form
2063
2214
  const passwordForm = useForm({
2064
2215
  initialValues: {
@@ -2590,7 +2741,7 @@ function UserProfile({
2590
2741
  })]
2591
2742
  })]
2592
2743
  })]
2593
- }), showPassword && /*#__PURE__*/jsxs(Box, {
2744
+ }), (showPassword || showSessions) && /*#__PURE__*/jsxs(Box, {
2594
2745
  mb: "md",
2595
2746
  children: [/*#__PURE__*/jsx(SectionHeader, {
2596
2747
  icon: IconShield,
@@ -2598,69 +2749,196 @@ function UserProfile({
2598
2749
  description: labels.securityDescription || 'Protect your account'
2599
2750
  }), /*#__PURE__*/jsxs(Stack, {
2600
2751
  gap: "sm",
2601
- children: [/*#__PURE__*/jsx(SettingRow, {
2602
- label: labels.password || 'Password',
2603
- action: labels.change || 'Change',
2604
- actionLabel: labels.changePassword || 'Change your password',
2605
- onClick: () => handleToggleSection('password'),
2606
- expanded: editingSection === 'password',
2607
- children: /*#__PURE__*/jsxs(Group, {
2608
- gap: "xs",
2609
- children: [/*#__PURE__*/jsx(IconKey, {
2610
- size: 16,
2611
- color: "var(--mantine-color-dimmed)"
2612
- }), /*#__PURE__*/jsx(Text, {
2613
- size: "sm",
2614
- c: "dimmed",
2615
- children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"
2616
- })]
2617
- })
2618
- }), /*#__PURE__*/jsx(Collapse, {
2619
- in: editingSection === 'password',
2620
- children: /*#__PURE__*/jsx(Card, {
2621
- p: "md",
2622
- withBorder: true,
2623
- children: /*#__PURE__*/jsx("form", {
2624
- onSubmit: passwordForm.onSubmit(handleChangePassword),
2625
- children: /*#__PURE__*/jsxs(Stack, {
2626
- gap: "sm",
2627
- children: [/*#__PURE__*/jsx(PasswordInput, {
2628
- label: labels.currentPassword || 'Senha Atual',
2629
- placeholder: labels.currentPasswordPlaceholder || 'Digite sua senha atual',
2630
- ...passwordForm.getInputProps('currentPassword')
2631
- }), /*#__PURE__*/jsx(PasswordInput, {
2632
- label: labels.newPassword || 'Nova Senha',
2633
- placeholder: labels.newPasswordPlaceholder || 'Digite a nova senha',
2634
- description: labels.passwordDescription || 'Mínimo 8 caracteres',
2635
- ...passwordForm.getInputProps('newPassword')
2636
- }), /*#__PURE__*/jsx(PasswordInput, {
2637
- label: labels.confirmPassword || 'Confirmar Nova Senha',
2638
- placeholder: labels.confirmPasswordPlaceholder || 'Confirme a nova senha',
2639
- ...passwordForm.getInputProps('confirmPassword')
2640
- }), /*#__PURE__*/jsxs(Group, {
2641
- justify: "flex-end",
2642
- gap: "xs",
2643
- children: [/*#__PURE__*/jsx(Button, {
2644
- variant: "default",
2645
- size: "xs",
2646
- onClick: () => handleToggleSection('password'),
2647
- leftSection: /*#__PURE__*/jsx(IconX, {
2648
- size: 14
2649
- }),
2650
- children: labels.cancel || 'Cancelar'
2651
- }), /*#__PURE__*/jsx(Button, {
2652
- type: "submit",
2653
- size: "xs",
2654
- loading: loadingChangePassword,
2655
- leftSection: /*#__PURE__*/jsx(IconCheck, {
2656
- size: 14
2657
- }),
2658
- children: labels.save || 'Salvar'
2752
+ children: [showPassword && /*#__PURE__*/jsxs(Fragment, {
2753
+ children: [/*#__PURE__*/jsx(SettingRow, {
2754
+ label: labels.password || 'Password',
2755
+ action: labels.change || 'Change',
2756
+ actionLabel: labels.changePassword || 'Change your password',
2757
+ onClick: () => handleToggleSection('password'),
2758
+ expanded: editingSection === 'password',
2759
+ children: /*#__PURE__*/jsxs(Group, {
2760
+ gap: "xs",
2761
+ children: [/*#__PURE__*/jsx(IconKey, {
2762
+ size: 16,
2763
+ color: "var(--mantine-color-dimmed)"
2764
+ }), /*#__PURE__*/jsx(Text, {
2765
+ size: "sm",
2766
+ c: "dimmed",
2767
+ children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"
2768
+ })]
2769
+ })
2770
+ }), /*#__PURE__*/jsx(Collapse, {
2771
+ in: editingSection === 'password',
2772
+ children: /*#__PURE__*/jsx(Card, {
2773
+ p: "md",
2774
+ withBorder: true,
2775
+ children: /*#__PURE__*/jsx("form", {
2776
+ onSubmit: passwordForm.onSubmit(handleChangePassword),
2777
+ children: /*#__PURE__*/jsxs(Stack, {
2778
+ gap: "sm",
2779
+ children: [/*#__PURE__*/jsx(PasswordInput, {
2780
+ label: labels.currentPassword || 'Senha Atual',
2781
+ placeholder: labels.currentPasswordPlaceholder || 'Digite sua senha atual',
2782
+ ...passwordForm.getInputProps('currentPassword')
2783
+ }), /*#__PURE__*/jsx(PasswordInput, {
2784
+ label: labels.newPassword || 'Nova Senha',
2785
+ placeholder: labels.newPasswordPlaceholder || 'Digite a nova senha',
2786
+ description: labels.passwordDescription || 'Mínimo 8 caracteres',
2787
+ ...passwordForm.getInputProps('newPassword')
2788
+ }), /*#__PURE__*/jsx(PasswordInput, {
2789
+ label: labels.confirmPassword || 'Confirmar Nova Senha',
2790
+ placeholder: labels.confirmPasswordPlaceholder || 'Confirme a nova senha',
2791
+ ...passwordForm.getInputProps('confirmPassword')
2792
+ }), /*#__PURE__*/jsxs(Group, {
2793
+ justify: "flex-end",
2794
+ gap: "xs",
2795
+ children: [/*#__PURE__*/jsx(Button, {
2796
+ variant: "default",
2797
+ size: "xs",
2798
+ onClick: () => handleToggleSection('password'),
2799
+ leftSection: /*#__PURE__*/jsx(IconX, {
2800
+ size: 14
2801
+ }),
2802
+ children: labels.cancel || 'Cancelar'
2803
+ }), /*#__PURE__*/jsx(Button, {
2804
+ type: "submit",
2805
+ size: "xs",
2806
+ loading: loadingChangePassword,
2807
+ leftSection: /*#__PURE__*/jsx(IconCheck, {
2808
+ size: 14
2809
+ }),
2810
+ children: labels.save || 'Salvar'
2811
+ })]
2659
2812
  })]
2660
- })]
2813
+ })
2661
2814
  })
2662
2815
  })
2663
- })
2816
+ })]
2817
+ }), showSessions && /*#__PURE__*/jsxs(Fragment, {
2818
+ children: [/*#__PURE__*/jsx(SettingRow, {
2819
+ label: labels.sessions || 'Sessions',
2820
+ action: editingSection === 'sessions' ? labels.close || 'Close' : labels.manage || 'Manage',
2821
+ actionLabel: labels.manageSessions || 'Manage your active sessions',
2822
+ onClick: () => handleToggleSection('sessions'),
2823
+ expanded: editingSection === 'sessions',
2824
+ children: /*#__PURE__*/jsxs(Group, {
2825
+ gap: "xs",
2826
+ children: [/*#__PURE__*/jsx(IconDevices, {
2827
+ size: 16,
2828
+ color: "var(--mantine-color-dimmed)"
2829
+ }), /*#__PURE__*/jsx(Text, {
2830
+ size: "sm",
2831
+ c: "dimmed",
2832
+ children: sessions.length > 0 ? `${sessions.length} active session${sessions.length > 1 ? 's' : ''}` : loadingListSessions ? 'Loading...' : 'No sessions'
2833
+ })]
2834
+ })
2835
+ }), /*#__PURE__*/jsx(Collapse, {
2836
+ in: editingSection === 'sessions',
2837
+ children: /*#__PURE__*/jsx(Card, {
2838
+ p: "md",
2839
+ withBorder: true,
2840
+ children: /*#__PURE__*/jsx(Stack, {
2841
+ gap: "md",
2842
+ children: loadingListSessions ? /*#__PURE__*/jsx(Text, {
2843
+ size: "sm",
2844
+ c: "dimmed",
2845
+ ta: "center",
2846
+ children: labels.loadingSessions || 'Carregando sessões...'
2847
+ }) : sessions.length === 0 ? /*#__PURE__*/jsx(Text, {
2848
+ size: "sm",
2849
+ c: "dimmed",
2850
+ ta: "center",
2851
+ children: labels.noSessionsFound || 'Nenhuma sessão encontrada'
2852
+ }) : /*#__PURE__*/jsxs(Fragment, {
2853
+ children: [sessions.map(sessionItem => {
2854
+ const isCurrentSession = sessionItem.id === currentSession?.id;
2855
+ const deviceInfo = parseUserAgent(sessionItem.userAgent);
2856
+ const createdDate = new Date(sessionItem.createdAt);
2857
+ return /*#__PURE__*/jsx(Card, {
2858
+ p: "sm",
2859
+ style: {
2860
+ borderColor: isCurrentSession ? 'var(--mantine-color-blue-5)' : undefined,
2861
+ backgroundColor: isCurrentSession ? 'var(--mantine-color-blue-light)' : undefined
2862
+ },
2863
+ children: /*#__PURE__*/jsxs(Group, {
2864
+ justify: "space-between",
2865
+ wrap: "nowrap",
2866
+ children: [/*#__PURE__*/jsxs(Group, {
2867
+ gap: "sm",
2868
+ wrap: "nowrap",
2869
+ style: {
2870
+ flex: 1
2871
+ },
2872
+ children: [/*#__PURE__*/jsx(ThemeIcon, {
2873
+ size: 36,
2874
+ variant: "light",
2875
+ color: isCurrentSession ? 'blue' : 'gray',
2876
+ children: /*#__PURE__*/jsx(IconDeviceMobile, {
2877
+ size: 18
2878
+ })
2879
+ }), /*#__PURE__*/jsxs(Box, {
2880
+ style: {
2881
+ flex: 1
2882
+ },
2883
+ children: [/*#__PURE__*/jsxs(Group, {
2884
+ gap: "xs",
2885
+ children: [/*#__PURE__*/jsx(Text, {
2886
+ size: "sm",
2887
+ fw: 500,
2888
+ children: deviceInfo.browser
2889
+ }), isCurrentSession && /*#__PURE__*/jsx(Badge, {
2890
+ size: "xs",
2891
+ variant: "filled",
2892
+ color: "blue",
2893
+ children: labels.thisDevice || 'Este dispositivo'
2894
+ })]
2895
+ }), /*#__PURE__*/jsxs(Text, {
2896
+ size: "xs",
2897
+ c: "dimmed",
2898
+ children: [deviceInfo.os, " \u2022 ", sessionItem.ipAddress || labels.unknownIP || 'IP desconhecido']
2899
+ }), /*#__PURE__*/jsxs(Text, {
2900
+ size: "xs",
2901
+ c: "dimmed",
2902
+ children: [labels.createdAt || 'Criada em', " ", createdDate.toLocaleDateString('pt-BR'), " ", labels.at || 'às', ' ', createdDate.toLocaleTimeString('pt-BR', {
2903
+ hour: '2-digit',
2904
+ minute: '2-digit'
2905
+ })]
2906
+ })]
2907
+ })]
2908
+ }), /*#__PURE__*/jsx(Tooltip, {
2909
+ label: isCurrentSession ? labels.signOutAndEnd || 'Encerrar e sair' : labels.endSession || 'Encerrar sessão',
2910
+ children: /*#__PURE__*/jsx(Button, {
2911
+ variant: "light",
2912
+ color: "red",
2913
+ size: "xs",
2914
+ onClick: () => handleRevokeSession(sessionItem.id),
2915
+ loading: loadingRevokeSession,
2916
+ leftSection: /*#__PURE__*/jsx(IconLogout, {
2917
+ size: 14
2918
+ }),
2919
+ children: labels.end || 'Encerrar'
2920
+ })
2921
+ })]
2922
+ })
2923
+ }, sessionItem.id);
2924
+ }), sessions.length > 1 && /*#__PURE__*/jsx(Group, {
2925
+ justify: "flex-end",
2926
+ children: /*#__PURE__*/jsx(Button, {
2927
+ variant: "light",
2928
+ color: "red",
2929
+ size: "xs",
2930
+ onClick: handleRevokeOtherSessions,
2931
+ loading: loadingRevokeSession,
2932
+ leftSection: /*#__PURE__*/jsx(IconLogout, {
2933
+ size: 14
2934
+ }),
2935
+ children: labels.endOtherSessions || 'Encerrar todas as outras sessões'
2936
+ })
2937
+ })]
2938
+ })
2939
+ })
2940
+ })
2941
+ })]
2664
2942
  })]
2665
2943
  })]
2666
2944
  })]
@@ -2844,5 +3122,5 @@ function SignOutButton({
2844
3122
  });
2845
3123
  }
2846
3124
 
2847
- 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, useSignIn, useSignOut, useSignUp, useUser, verifyEmail, verifyMagicLink };
3125
+ 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 };
2848
3126
  //# sourceMappingURL=index.esm.js.map