@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.js CHANGED
@@ -235,7 +235,7 @@ const verifyMagicLink = async token => {
235
235
  /*--- Password Reset -----------------------------*/
236
236
  const forgotPassword = async (email, redirectTo) => {
237
237
  const redirect = redirectTo || (typeof window !== 'undefined' ? `${window.location.origin}/auth/reset-password` : '/auth/reset-password');
238
- return await api('/auth/forget-password', {
238
+ return await api('/auth/request-password-reset', {
239
239
  method: 'POST',
240
240
  body: JSON.stringify({
241
241
  email,
@@ -273,6 +273,22 @@ const resendVerification = async (email, callbackURL) => {
273
273
  const getSession = async () => {
274
274
  return await api('/auth/session');
275
275
  };
276
+ const listSessions = async () => {
277
+ return await api('/auth/list-sessions');
278
+ };
279
+ const revokeSession = async id => {
280
+ return await api('/auth/revoke-session-by-id', {
281
+ method: 'POST',
282
+ body: JSON.stringify({
283
+ id
284
+ })
285
+ });
286
+ };
287
+ const revokeOtherSessions = async () => {
288
+ return await api('/auth/revoke-other-sessions', {
289
+ method: 'POST'
290
+ });
291
+ };
276
292
 
277
293
  /*--- Application Info ----------------------------*/
278
294
  const getApplicationInfo = async () => {
@@ -324,6 +340,9 @@ const useAuthStore = zustand.create((set, get) => ({
324
340
  user: null,
325
341
  loading: true,
326
342
  error: null,
343
+ // Session management
344
+ sessions: [],
345
+ currentSession: null,
327
346
  // Loading states granulares
328
347
  loadingStates: {
329
348
  signIn: false,
@@ -336,7 +355,9 @@ const useAuthStore = zustand.create((set, get) => ({
336
355
  resendVerification: false,
337
356
  updateProfile: false,
338
357
  changePassword: false,
339
- changeEmail: false
358
+ changeEmail: false,
359
+ listSessions: false,
360
+ revokeSession: false
340
361
  },
341
362
  // Application info (logo, nome, etc)
342
363
  applicationInfo: null,
@@ -379,9 +400,14 @@ const useAuthStore = zustand.create((set, get) => ({
379
400
  // Precisamos buscar a sessão no servidor.
380
401
  if (!user) {
381
402
  try {
382
- const session = await getSession();
383
- if (session?.user) {
384
- 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
+ });
385
411
  }
386
412
  } catch (sessionError) {
387
413
  console.warn('[AuthStore] Failed to fetch session:', sessionError);
@@ -454,8 +480,13 @@ const useAuthStore = zustand.create((set, get) => ({
454
480
 
455
481
  // Se não encontrou no token (sessão opaca), busca do servidor
456
482
  if (!user) {
457
- const session = await getSession();
458
- 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
+ }
459
490
  }
460
491
  set({
461
492
  user,
@@ -637,11 +668,86 @@ const useAuthStore = zustand.create((set, get) => ({
637
668
  /* Session */
638
669
  getSession: async () => {
639
670
  try {
640
- return await getSession();
671
+ const sessionData = await getSession();
672
+ // Store current session for comparison (includes id)
673
+ if (sessionData?.session) {
674
+ set({
675
+ currentSession: sessionData.session
676
+ });
677
+ }
678
+ return sessionData;
679
+ } catch (err) {
680
+ set({
681
+ error: err
682
+ });
683
+ throw err;
684
+ }
685
+ },
686
+ listSessions: async () => {
687
+ const {
688
+ setLoading
689
+ } = get();
690
+ setLoading('listSessions', true);
691
+ set({
692
+ error: null
693
+ });
694
+ try {
695
+ const result = await listSessions();
696
+ set({
697
+ sessions: result || []
698
+ });
699
+ setLoading('listSessions', false);
700
+ return result;
701
+ } catch (err) {
702
+ set({
703
+ error: err,
704
+ sessions: []
705
+ });
706
+ setLoading('listSessions', false);
707
+ throw err;
708
+ }
709
+ },
710
+ revokeSession: async token => {
711
+ const {
712
+ setLoading,
713
+ listSessions
714
+ } = get();
715
+ setLoading('revokeSession', true);
716
+ set({
717
+ error: null
718
+ });
719
+ try {
720
+ await revokeSession(token);
721
+ // Refresh sessions list after revocation
722
+ await listSessions();
723
+ setLoading('revokeSession', false);
724
+ } catch (err) {
725
+ set({
726
+ error: err
727
+ });
728
+ setLoading('revokeSession', false);
729
+ throw err;
730
+ }
731
+ },
732
+ revokeOtherSessions: async () => {
733
+ const {
734
+ setLoading,
735
+ listSessions
736
+ } = get();
737
+ setLoading('revokeSession', true);
738
+ set({
739
+ error: null
740
+ });
741
+ try {
742
+ await revokeOtherSessions();
743
+ // Refresh sessions list after revocation
744
+ await listSessions();
745
+ setLoading('revokeSession', false);
641
746
  } catch (err) {
642
747
  set({
643
748
  error: err
644
749
  });
750
+ setLoading('revokeSession', false);
645
751
  throw err;
646
752
  }
647
753
  },
@@ -821,8 +927,24 @@ function AuthProvider({
821
927
  checkTokenValidity();
822
928
  }
823
929
  };
930
+
931
+ // Escuta evento de sessão revogada (quando o usuário revoga sua própria sessão)
932
+ const handleSessionRevoked = () => {
933
+ // Limpa o usuário do store
934
+ useAuthStore.setState({
935
+ user: null,
936
+ currentSession: null,
937
+ sessions: []
938
+ });
939
+ // Dispara evento de logout para sincronizar entre abas
940
+ localStorage.setItem('auth:logout', Date.now());
941
+ };
824
942
  window.addEventListener('storage', handleStorageChange);
825
- return () => window.removeEventListener('storage', handleStorageChange);
943
+ window.addEventListener('auth:session-revoked', handleSessionRevoked);
944
+ return () => {
945
+ window.removeEventListener('storage', handleStorageChange);
946
+ window.removeEventListener('auth:session-revoked', handleSessionRevoked);
947
+ };
826
948
  }, [checkTokenValidity]);
827
949
 
828
950
  // Verifica validade do token periodicamente
@@ -910,6 +1032,19 @@ const useUser = () => useAuthStore(shallow.useShallow(s => ({
910
1032
  // Alias deprecado para backwards compatibility
911
1033
  const useProfile = useUser;
912
1034
 
1035
+ // Sessions Management Hook
1036
+ const useSessions = () => useAuthStore(shallow.useShallow(s => ({
1037
+ currentSession: s.currentSession,
1038
+ sessions: s.sessions,
1039
+ getSession: s.getSession,
1040
+ listSessions: s.listSessions,
1041
+ revokeSession: s.revokeSession,
1042
+ revokeOtherSessions: s.revokeOtherSessions,
1043
+ loadingListSessions: s.loadingStates.listSessions,
1044
+ loadingRevokeSession: s.loadingStates.revokeSession,
1045
+ error: s.error
1046
+ })));
1047
+
913
1048
  // Application Logo Hook
914
1049
  const useApplicationLogo = () => {
915
1050
  const applicationInfo = useAuthStore(s => s.applicationInfo);
@@ -1939,6 +2074,7 @@ function UserProfile({
1939
2074
  showName = true,
1940
2075
  showEmail = true,
1941
2076
  showPassword = true,
2077
+ showSessions = true,
1942
2078
  // Customização
1943
2079
  labels = {},
1944
2080
  title = 'Account',
@@ -1966,6 +2102,111 @@ function UserProfile({
1966
2102
  loadingChangeEmail
1967
2103
  } = useProfile();
1968
2104
 
2105
+ // Hook para sessions
2106
+ const {
2107
+ currentSession,
2108
+ sessions,
2109
+ listSessions,
2110
+ getSession,
2111
+ revokeSession,
2112
+ revokeOtherSessions,
2113
+ loadingListSessions,
2114
+ loadingRevokeSession
2115
+ } = useSessions();
2116
+
2117
+ // Load sessions when opened (modal) or component mounts (card), and when sessions section is opened
2118
+ react.useEffect(() => {
2119
+ if (showSessions && (variant === 'card' || opened)) {
2120
+ // Fetch current session first to get the session ID, then list all sessions
2121
+ getSession().catch(err => console.warn('Failed to get current session:', err));
2122
+ listSessions().catch(err => console.warn('Failed to load sessions:', err));
2123
+ }
2124
+ }, [opened, showSessions, variant]);
2125
+
2126
+ // Helper to parse user agent string
2127
+ const parseUserAgent = ua => {
2128
+ if (!ua) return {
2129
+ browser: 'Unknown Browser',
2130
+ os: 'Unknown OS'
2131
+ };
2132
+ let browser = 'Unknown Browser';
2133
+ let os = 'Unknown OS';
2134
+
2135
+ // Detect browser
2136
+ 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';
2137
+
2138
+ // Detect OS
2139
+ 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';
2140
+ return {
2141
+ browser,
2142
+ os
2143
+ };
2144
+ };
2145
+
2146
+ // Session handlers
2147
+ const handleRevokeSession = async sessionId => {
2148
+ // Check if revoking current session
2149
+ const isCurrentSession = sessionId === currentSession?.id || sessions.length === 1;
2150
+
2151
+ // If revoking current session, handle logout immediately after revocation
2152
+ if (isCurrentSession) {
2153
+ try {
2154
+ await revokeSession(sessionId);
2155
+ } catch (error) {
2156
+ // Even if it fails, we're revoking our own session, so just logout
2157
+ // The server already revoked our session
2158
+ }
2159
+ // Clear auth state and redirect
2160
+ localStorage.removeItem('auth:token');
2161
+ window.dispatchEvent(new CustomEvent('auth:session-revoked'));
2162
+ if (variant === 'modal') onClose?.();
2163
+ return;
2164
+ }
2165
+
2166
+ // Revoking another session
2167
+ try {
2168
+ await revokeSession(sessionId);
2169
+ notifications.notifications.show({
2170
+ title: labels.successTitle || 'Sucesso',
2171
+ message: labels.sessionRevoked || 'Sessão encerrada com sucesso',
2172
+ color: 'green'
2173
+ });
2174
+ } catch (error) {
2175
+ // Check for 401 error (our SDK uses error.res, axios uses error.response)
2176
+ const status = error.res?.status || error.response?.status;
2177
+ if (status === 401) {
2178
+ // This means our session was revoked, not the target one - do logout
2179
+ localStorage.removeItem('auth:token');
2180
+ window.dispatchEvent(new CustomEvent('auth:session-revoked'));
2181
+ if (variant === 'modal') onClose?.();
2182
+ return;
2183
+ }
2184
+ notifications.notifications.show({
2185
+ title: labels.errorTitle || 'Erro',
2186
+ message: error.message || labels.sessionRevokeFailed || 'Falha ao encerrar sessão',
2187
+ color: 'red'
2188
+ });
2189
+ onError?.(error);
2190
+ }
2191
+ };
2192
+ const handleRevokeOtherSessions = async () => {
2193
+ try {
2194
+ await revokeOtherSessions();
2195
+ notifications.notifications.show({
2196
+ title: labels.successTitle || 'Sucesso',
2197
+ message: labels.otherSessionsRevoked || 'Todas as outras sessões foram encerradas',
2198
+ color: 'green'
2199
+ });
2200
+ } catch (error) {
2201
+ notifications.notifications.show({
2202
+ title: labels.errorTitle || 'Erro',
2203
+ message: error.message || labels.otherSessionsRevokeFailed || 'Falha ao encerrar sessões',
2204
+ color: 'red'
2205
+ });
2206
+ onError?.(error);
2207
+ }
2208
+ };
2209
+
1969
2210
  // Password form
1970
2211
  const passwordForm = form.useForm({
1971
2212
  initialValues: {
@@ -2497,7 +2738,7 @@ function UserProfile({
2497
2738
  })]
2498
2739
  })]
2499
2740
  })]
2500
- }), showPassword && /*#__PURE__*/jsxRuntime.jsxs(core.Box, {
2741
+ }), (showPassword || showSessions) && /*#__PURE__*/jsxRuntime.jsxs(core.Box, {
2501
2742
  mb: "md",
2502
2743
  children: [/*#__PURE__*/jsxRuntime.jsx(SectionHeader, {
2503
2744
  icon: iconsReact.IconShield,
@@ -2505,69 +2746,196 @@ function UserProfile({
2505
2746
  description: labels.securityDescription || 'Protect your account'
2506
2747
  }), /*#__PURE__*/jsxRuntime.jsxs(core.Stack, {
2507
2748
  gap: "sm",
2508
- children: [/*#__PURE__*/jsxRuntime.jsx(SettingRow, {
2509
- label: labels.password || 'Password',
2510
- action: labels.change || 'Change',
2511
- actionLabel: labels.changePassword || 'Change your password',
2512
- onClick: () => handleToggleSection('password'),
2513
- expanded: editingSection === 'password',
2514
- children: /*#__PURE__*/jsxRuntime.jsxs(core.Group, {
2515
- gap: "xs",
2516
- children: [/*#__PURE__*/jsxRuntime.jsx(iconsReact.IconKey, {
2517
- size: 16,
2518
- color: "var(--mantine-color-dimmed)"
2519
- }), /*#__PURE__*/jsxRuntime.jsx(core.Text, {
2520
- size: "sm",
2521
- c: "dimmed",
2522
- children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"
2523
- })]
2524
- })
2525
- }), /*#__PURE__*/jsxRuntime.jsx(core.Collapse, {
2526
- in: editingSection === 'password',
2527
- children: /*#__PURE__*/jsxRuntime.jsx(core.Card, {
2528
- p: "md",
2529
- withBorder: true,
2530
- children: /*#__PURE__*/jsxRuntime.jsx("form", {
2531
- onSubmit: passwordForm.onSubmit(handleChangePassword),
2532
- children: /*#__PURE__*/jsxRuntime.jsxs(core.Stack, {
2533
- gap: "sm",
2534
- children: [/*#__PURE__*/jsxRuntime.jsx(core.PasswordInput, {
2535
- label: labels.currentPassword || 'Senha Atual',
2536
- placeholder: labels.currentPasswordPlaceholder || 'Digite sua senha atual',
2537
- ...passwordForm.getInputProps('currentPassword')
2538
- }), /*#__PURE__*/jsxRuntime.jsx(core.PasswordInput, {
2539
- label: labels.newPassword || 'Nova Senha',
2540
- placeholder: labels.newPasswordPlaceholder || 'Digite a nova senha',
2541
- description: labels.passwordDescription || 'Mínimo 8 caracteres',
2542
- ...passwordForm.getInputProps('newPassword')
2543
- }), /*#__PURE__*/jsxRuntime.jsx(core.PasswordInput, {
2544
- label: labels.confirmPassword || 'Confirmar Nova Senha',
2545
- placeholder: labels.confirmPasswordPlaceholder || 'Confirme a nova senha',
2546
- ...passwordForm.getInputProps('confirmPassword')
2547
- }), /*#__PURE__*/jsxRuntime.jsxs(core.Group, {
2548
- justify: "flex-end",
2549
- gap: "xs",
2550
- children: [/*#__PURE__*/jsxRuntime.jsx(core.Button, {
2551
- variant: "default",
2552
- size: "xs",
2553
- onClick: () => handleToggleSection('password'),
2554
- leftSection: /*#__PURE__*/jsxRuntime.jsx(iconsReact.IconX, {
2555
- size: 14
2556
- }),
2557
- children: labels.cancel || 'Cancelar'
2558
- }), /*#__PURE__*/jsxRuntime.jsx(core.Button, {
2559
- type: "submit",
2560
- size: "xs",
2561
- loading: loadingChangePassword,
2562
- leftSection: /*#__PURE__*/jsxRuntime.jsx(iconsReact.IconCheck, {
2563
- size: 14
2564
- }),
2565
- children: labels.save || 'Salvar'
2749
+ children: [showPassword && /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
2750
+ children: [/*#__PURE__*/jsxRuntime.jsx(SettingRow, {
2751
+ label: labels.password || 'Password',
2752
+ action: labels.change || 'Change',
2753
+ actionLabel: labels.changePassword || 'Change your password',
2754
+ onClick: () => handleToggleSection('password'),
2755
+ expanded: editingSection === 'password',
2756
+ children: /*#__PURE__*/jsxRuntime.jsxs(core.Group, {
2757
+ gap: "xs",
2758
+ children: [/*#__PURE__*/jsxRuntime.jsx(iconsReact.IconKey, {
2759
+ size: 16,
2760
+ color: "var(--mantine-color-dimmed)"
2761
+ }), /*#__PURE__*/jsxRuntime.jsx(core.Text, {
2762
+ size: "sm",
2763
+ c: "dimmed",
2764
+ children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"
2765
+ })]
2766
+ })
2767
+ }), /*#__PURE__*/jsxRuntime.jsx(core.Collapse, {
2768
+ in: editingSection === 'password',
2769
+ children: /*#__PURE__*/jsxRuntime.jsx(core.Card, {
2770
+ p: "md",
2771
+ withBorder: true,
2772
+ children: /*#__PURE__*/jsxRuntime.jsx("form", {
2773
+ onSubmit: passwordForm.onSubmit(handleChangePassword),
2774
+ children: /*#__PURE__*/jsxRuntime.jsxs(core.Stack, {
2775
+ gap: "sm",
2776
+ children: [/*#__PURE__*/jsxRuntime.jsx(core.PasswordInput, {
2777
+ label: labels.currentPassword || 'Senha Atual',
2778
+ placeholder: labels.currentPasswordPlaceholder || 'Digite sua senha atual',
2779
+ ...passwordForm.getInputProps('currentPassword')
2780
+ }), /*#__PURE__*/jsxRuntime.jsx(core.PasswordInput, {
2781
+ label: labels.newPassword || 'Nova Senha',
2782
+ placeholder: labels.newPasswordPlaceholder || 'Digite a nova senha',
2783
+ description: labels.passwordDescription || 'Mínimo 8 caracteres',
2784
+ ...passwordForm.getInputProps('newPassword')
2785
+ }), /*#__PURE__*/jsxRuntime.jsx(core.PasswordInput, {
2786
+ label: labels.confirmPassword || 'Confirmar Nova Senha',
2787
+ placeholder: labels.confirmPasswordPlaceholder || 'Confirme a nova senha',
2788
+ ...passwordForm.getInputProps('confirmPassword')
2789
+ }), /*#__PURE__*/jsxRuntime.jsxs(core.Group, {
2790
+ justify: "flex-end",
2791
+ gap: "xs",
2792
+ children: [/*#__PURE__*/jsxRuntime.jsx(core.Button, {
2793
+ variant: "default",
2794
+ size: "xs",
2795
+ onClick: () => handleToggleSection('password'),
2796
+ leftSection: /*#__PURE__*/jsxRuntime.jsx(iconsReact.IconX, {
2797
+ size: 14
2798
+ }),
2799
+ children: labels.cancel || 'Cancelar'
2800
+ }), /*#__PURE__*/jsxRuntime.jsx(core.Button, {
2801
+ type: "submit",
2802
+ size: "xs",
2803
+ loading: loadingChangePassword,
2804
+ leftSection: /*#__PURE__*/jsxRuntime.jsx(iconsReact.IconCheck, {
2805
+ size: 14
2806
+ }),
2807
+ children: labels.save || 'Salvar'
2808
+ })]
2566
2809
  })]
2567
- })]
2810
+ })
2568
2811
  })
2569
2812
  })
2570
- })
2813
+ })]
2814
+ }), showSessions && /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
2815
+ children: [/*#__PURE__*/jsxRuntime.jsx(SettingRow, {
2816
+ label: labels.sessions || 'Sessions',
2817
+ action: editingSection === 'sessions' ? labels.close || 'Close' : labels.manage || 'Manage',
2818
+ actionLabel: labels.manageSessions || 'Manage your active sessions',
2819
+ onClick: () => handleToggleSection('sessions'),
2820
+ expanded: editingSection === 'sessions',
2821
+ children: /*#__PURE__*/jsxRuntime.jsxs(core.Group, {
2822
+ gap: "xs",
2823
+ children: [/*#__PURE__*/jsxRuntime.jsx(iconsReact.IconDevices, {
2824
+ size: 16,
2825
+ color: "var(--mantine-color-dimmed)"
2826
+ }), /*#__PURE__*/jsxRuntime.jsx(core.Text, {
2827
+ size: "sm",
2828
+ c: "dimmed",
2829
+ children: sessions.length > 0 ? `${sessions.length} active session${sessions.length > 1 ? 's' : ''}` : loadingListSessions ? 'Loading...' : 'No sessions'
2830
+ })]
2831
+ })
2832
+ }), /*#__PURE__*/jsxRuntime.jsx(core.Collapse, {
2833
+ in: editingSection === 'sessions',
2834
+ children: /*#__PURE__*/jsxRuntime.jsx(core.Card, {
2835
+ p: "md",
2836
+ withBorder: true,
2837
+ children: /*#__PURE__*/jsxRuntime.jsx(core.Stack, {
2838
+ gap: "md",
2839
+ children: loadingListSessions ? /*#__PURE__*/jsxRuntime.jsx(core.Text, {
2840
+ size: "sm",
2841
+ c: "dimmed",
2842
+ ta: "center",
2843
+ children: labels.loadingSessions || 'Carregando sessões...'
2844
+ }) : sessions.length === 0 ? /*#__PURE__*/jsxRuntime.jsx(core.Text, {
2845
+ size: "sm",
2846
+ c: "dimmed",
2847
+ ta: "center",
2848
+ children: labels.noSessionsFound || 'Nenhuma sessão encontrada'
2849
+ }) : /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
2850
+ children: [sessions.map(sessionItem => {
2851
+ const isCurrentSession = sessionItem.id === currentSession?.id;
2852
+ const deviceInfo = parseUserAgent(sessionItem.userAgent);
2853
+ const createdDate = new Date(sessionItem.createdAt);
2854
+ return /*#__PURE__*/jsxRuntime.jsx(core.Card, {
2855
+ p: "sm",
2856
+ style: {
2857
+ borderColor: isCurrentSession ? 'var(--mantine-color-blue-5)' : undefined,
2858
+ backgroundColor: isCurrentSession ? 'var(--mantine-color-blue-light)' : undefined
2859
+ },
2860
+ children: /*#__PURE__*/jsxRuntime.jsxs(core.Group, {
2861
+ justify: "space-between",
2862
+ wrap: "nowrap",
2863
+ children: [/*#__PURE__*/jsxRuntime.jsxs(core.Group, {
2864
+ gap: "sm",
2865
+ wrap: "nowrap",
2866
+ style: {
2867
+ flex: 1
2868
+ },
2869
+ children: [/*#__PURE__*/jsxRuntime.jsx(core.ThemeIcon, {
2870
+ size: 36,
2871
+ variant: "light",
2872
+ color: isCurrentSession ? 'blue' : 'gray',
2873
+ children: /*#__PURE__*/jsxRuntime.jsx(iconsReact.IconDeviceMobile, {
2874
+ size: 18
2875
+ })
2876
+ }), /*#__PURE__*/jsxRuntime.jsxs(core.Box, {
2877
+ style: {
2878
+ flex: 1
2879
+ },
2880
+ children: [/*#__PURE__*/jsxRuntime.jsxs(core.Group, {
2881
+ gap: "xs",
2882
+ children: [/*#__PURE__*/jsxRuntime.jsx(core.Text, {
2883
+ size: "sm",
2884
+ fw: 500,
2885
+ children: deviceInfo.browser
2886
+ }), isCurrentSession && /*#__PURE__*/jsxRuntime.jsx(core.Badge, {
2887
+ size: "xs",
2888
+ variant: "filled",
2889
+ color: "blue",
2890
+ children: labels.thisDevice || 'Este dispositivo'
2891
+ })]
2892
+ }), /*#__PURE__*/jsxRuntime.jsxs(core.Text, {
2893
+ size: "xs",
2894
+ c: "dimmed",
2895
+ children: [deviceInfo.os, " \u2022 ", sessionItem.ipAddress || labels.unknownIP || 'IP desconhecido']
2896
+ }), /*#__PURE__*/jsxRuntime.jsxs(core.Text, {
2897
+ size: "xs",
2898
+ c: "dimmed",
2899
+ children: [labels.createdAt || 'Criada em', " ", createdDate.toLocaleDateString('pt-BR'), " ", labels.at || 'às', ' ', createdDate.toLocaleTimeString('pt-BR', {
2900
+ hour: '2-digit',
2901
+ minute: '2-digit'
2902
+ })]
2903
+ })]
2904
+ })]
2905
+ }), /*#__PURE__*/jsxRuntime.jsx(core.Tooltip, {
2906
+ label: isCurrentSession ? labels.signOutAndEnd || 'Encerrar e sair' : labels.endSession || 'Encerrar sessão',
2907
+ children: /*#__PURE__*/jsxRuntime.jsx(core.Button, {
2908
+ variant: "light",
2909
+ color: "red",
2910
+ size: "xs",
2911
+ onClick: () => handleRevokeSession(sessionItem.id),
2912
+ loading: loadingRevokeSession,
2913
+ leftSection: /*#__PURE__*/jsxRuntime.jsx(iconsReact.IconLogout, {
2914
+ size: 14
2915
+ }),
2916
+ children: labels.end || 'Encerrar'
2917
+ })
2918
+ })]
2919
+ })
2920
+ }, sessionItem.id);
2921
+ }), sessions.length > 1 && /*#__PURE__*/jsxRuntime.jsx(core.Group, {
2922
+ justify: "flex-end",
2923
+ children: /*#__PURE__*/jsxRuntime.jsx(core.Button, {
2924
+ variant: "light",
2925
+ color: "red",
2926
+ size: "xs",
2927
+ onClick: handleRevokeOtherSessions,
2928
+ loading: loadingRevokeSession,
2929
+ leftSection: /*#__PURE__*/jsxRuntime.jsx(iconsReact.IconLogout, {
2930
+ size: 14
2931
+ }),
2932
+ children: labels.endOtherSessions || 'Encerrar todas as outras sessões'
2933
+ })
2934
+ })]
2935
+ })
2936
+ })
2937
+ })
2938
+ })]
2571
2939
  })]
2572
2940
  })]
2573
2941
  })]
@@ -2787,9 +3155,12 @@ exports.getApplicationInfo = getApplicationInfo;
2787
3155
  exports.getCurrentUser = getCurrentUser;
2788
3156
  exports.getSession = getSession;
2789
3157
  exports.isAuthenticated = isAuthenticated;
3158
+ exports.listSessions = listSessions;
2790
3159
  exports.refreshToken = refreshToken;
2791
3160
  exports.resendVerification = resendVerification;
2792
3161
  exports.resetPassword = resetPassword;
3162
+ exports.revokeOtherSessions = revokeOtherSessions;
3163
+ exports.revokeSession = revokeSession;
2793
3164
  exports.sendMagicLink = sendMagicLink;
2794
3165
  exports.signIn = signIn;
2795
3166
  exports.signOut = signOut;
@@ -2806,6 +3177,7 @@ exports.useMagicLink = useMagicLink;
2806
3177
  exports.usePasswordReset = usePasswordReset;
2807
3178
  exports.useProfile = useUser;
2808
3179
  exports.useSession = useSession;
3180
+ exports.useSessions = useSessions;
2809
3181
  exports.useSignIn = useSignIn;
2810
3182
  exports.useSignOut = useSignOut;
2811
3183
  exports.useSignUp = useSignUp;