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