UNPKG

@oxyhq/services

Version:

Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀

423 lines (414 loc) • 15.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = require("react"); var _reactNative = require("react-native"); var _OxyContext = require("../context/OxyContext"); var _sonner = require("../../lib/sonner"); var _confirmAction = require("../utils/confirmAction"); var _components = require("../components"); var _jsxRuntime = require("react/jsx-runtime"); const SessionManagementScreen = ({ onClose, theme, goBack }) => { const { sessions: userSessions, activeSessionId, refreshSessions, logout, logoutAll, switchSession } = (0, _OxyContext.useOxy)(); const [loading, setLoading] = (0, _react.useState)(true); const [refreshing, setRefreshing] = (0, _react.useState)(false); const [actionLoading, setActionLoading] = (0, _react.useState)(null); const [switchLoading, setSwitchLoading] = (0, _react.useState)(null); const [lastRefreshed, setLastRefreshed] = (0, _react.useState)(null); const isDarkTheme = theme === 'dark'; const textColor = isDarkTheme ? '#FFFFFF' : '#000000'; const backgroundColor = isDarkTheme ? '#121212' : '#FFFFFF'; const secondaryBackgroundColor = isDarkTheme ? '#222222' : '#F5F5F5'; const borderColor = isDarkTheme ? '#444444' : '#E0E0E0'; const primaryColor = '#0066CC'; const dangerColor = '#D32F2F'; const successColor = '#2E7D32'; // Memoized load sessions function - prevents unnecessary re-renders const loadSessions = (0, _react.useCallback)(async (isRefresh = false) => { try { if (isRefresh) { setRefreshing(true); } else { setLoading(true); } await refreshSessions(); setLastRefreshed(new Date()); } catch (error) { console.error('Failed to load sessions:', error); if (_reactNative.Platform.OS === 'web') { _sonner.toast.error('Failed to load sessions. Please try again.'); } else { _reactNative.Alert.alert('Error', 'Failed to load sessions. Please try again.', [{ text: 'OK' }]); } } finally { setLoading(false); setRefreshing(false); } }, [refreshSessions]); // Memoized logout session handler - prevents unnecessary re-renders const handleLogoutSession = (0, _react.useCallback)(async sessionId => { (0, _confirmAction.confirmAction)('Are you sure you want to logout this session?', async () => { try { setActionLoading(sessionId); await logout(sessionId); await refreshSessions(); _sonner.toast.success('Session logged out successfully'); } catch (error) { console.error('Logout session failed:', error); _sonner.toast.error('Failed to logout session. Please try again.'); } finally { setActionLoading(null); } }); }, [logout, refreshSessions]); // Memoized logout other sessions handler - prevents unnecessary re-renders const handleLogoutOtherSessions = (0, _react.useCallback)(async () => { const otherSessionsCount = userSessions.filter(s => s.sessionId !== activeSessionId).length; if (otherSessionsCount === 0) { _sonner.toast.info('No other sessions to logout.'); return; } (0, _confirmAction.confirmAction)(`This will logout ${otherSessionsCount} other session${otherSessionsCount > 1 ? 's' : ''}. Continue?`, async () => { try { setActionLoading('others'); for (const session of userSessions) { if (session.sessionId !== activeSessionId) { await logout(session.sessionId); } } await refreshSessions(); _sonner.toast.success('Other sessions logged out successfully'); } catch (error) { console.error('Logout other sessions failed:', error); _sonner.toast.error('Failed to logout other sessions. Please try again.'); } finally { setActionLoading(null); } }); }, [userSessions, activeSessionId, logout, refreshSessions]); // Memoized logout all sessions handler - prevents unnecessary re-renders const handleLogoutAllSessions = (0, _react.useCallback)(async () => { (0, _confirmAction.confirmAction)('This will logout all sessions including this one and you will need to sign in again. Continue?', async () => { try { setActionLoading('all'); await logoutAll(); } catch (error) { console.error('Logout all sessions failed:', error); _sonner.toast.error('Failed to logout all sessions. Please try again.'); setActionLoading(null); } }); }, [logoutAll]); // Memoized relative time formatter - prevents function recreation on every render const formatRelative = (0, _react.useCallback)(dateString => { if (!dateString) return 'Unknown'; const date = new Date(dateString); const now = new Date(); const diffMs = date.getTime() - now.getTime(); const absMin = Math.abs(diffMs) / 60000; const isFuture = diffMs > 0; const fmt = n => n < 1 ? 'moments' : Math.floor(n); if (absMin < 1) return isFuture ? 'in moments' : 'just now'; if (absMin < 60) return isFuture ? `in ${fmt(absMin)}m` : `${fmt(absMin)}m ago`; const hrs = absMin / 60; if (hrs < 24) return isFuture ? `in ${fmt(hrs)}h` : `${fmt(hrs)}h ago`; const days = hrs / 24; if (days < 7) return isFuture ? `in ${fmt(days)}d` : `${fmt(days)}d ago`; return date.toLocaleDateString(); }, []); // Memoized switch session handler - prevents unnecessary re-renders const handleSwitchSession = (0, _react.useCallback)(async sessionId => { if (sessionId === activeSessionId) return; setSwitchLoading(sessionId); try { await switchSession(sessionId); _sonner.toast.success('Switched session'); } catch (e) { console.error('Switch session failed', e); _sonner.toast.error('Failed to switch session'); } finally { setSwitchLoading(null); } }, [activeSessionId, switchSession]); (0, _react.useEffect)(() => { loadSessions(); }, [loadSessions]); if (loading) { return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: [styles.container, styles.centerContent, { backgroundColor }], children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, { size: "large", color: primaryColor }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.loadingText, { color: textColor }], children: "Loading sessions..." })] }); } // Memoized session items - prevents unnecessary re-renders when dependencies haven't changed const sessionItems = (0, _react.useMemo)(() => { return userSessions.map(session => { const isCurrent = session.sessionId === activeSessionId; const subtitleParts = []; if (session.deviceId) subtitleParts.push(`Device ${session.deviceId.substring(0, 10)}...`); subtitleParts.push(`Last ${formatRelative(session.lastActive)}`); subtitleParts.push(`Expires ${formatRelative(session.expiresAt)}`); return { id: session.sessionId, icon: isCurrent ? 'shield-checkmark' : 'laptop-outline', iconColor: isCurrent ? successColor : primaryColor, title: isCurrent ? 'Current Session' : `Session ${session.sessionId.substring(0, 8)}...`, subtitle: subtitleParts.join(' \u2022 '), showChevron: false, multiRow: true, customContentBelow: !isCurrent ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.sessionActionsRow, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { onPress: () => handleSwitchSession(session.sessionId), style: [styles.sessionPillButton, { backgroundColor: isDarkTheme ? '#1E2A38' : '#E6F2FF', borderColor: primaryColor }], disabled: switchLoading === session.sessionId || actionLoading === session.sessionId, children: switchLoading === session.sessionId ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, { size: "small", color: primaryColor }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.sessionPillText, { color: primaryColor }], children: "Switch" }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { onPress: () => handleLogoutSession(session.sessionId), style: [styles.sessionPillButton, { backgroundColor: isDarkTheme ? '#3A1E1E' : '#FFEBEE', borderColor: dangerColor }], disabled: actionLoading === session.sessionId || switchLoading === session.sessionId, children: actionLoading === session.sessionId ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, { size: "small", color: dangerColor }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.sessionPillText, { color: dangerColor }], children: "Logout" }) })] }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.sessionActionsRow, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.currentBadgeText, { color: successColor }], children: "Active" }) }), selected: isCurrent, dense: true }; }); }, [userSessions, activeSessionId, formatRelative, successColor, primaryColor, isDarkTheme, switchLoading, actionLoading, handleSwitchSession, handleLogoutSession, dangerColor]); // Memoized bulk action items - prevents unnecessary re-renders when dependencies haven't changed const otherSessionsCount = (0, _react.useMemo)(() => userSessions.filter(s => s.sessionId !== activeSessionId).length, [userSessions, activeSessionId]); const bulkItems = (0, _react.useMemo)(() => [{ id: 'logout-others', icon: 'exit-outline', iconColor: primaryColor, title: 'Logout Other Sessions', subtitle: otherSessionsCount === 0 ? 'No other sessions' : 'End all sessions except this one', onPress: handleLogoutOtherSessions, showChevron: false, customContent: actionLoading === 'others' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, { size: "small", color: primaryColor }) : undefined, disabled: actionLoading === 'others' || otherSessionsCount === 0, dense: true }, { id: 'logout-all', icon: 'warning-outline', iconColor: dangerColor, title: 'Logout All Sessions', subtitle: 'End all sessions including this one', onPress: handleLogoutAllSessions, showChevron: false, customContent: actionLoading === 'all' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, { size: "small", color: dangerColor }) : undefined, disabled: actionLoading === 'all', dense: true }], [otherSessionsCount, primaryColor, dangerColor, handleLogoutOtherSessions, handleLogoutAllSessions, actionLoading]); return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: [styles.container, { backgroundColor }], children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Header, { title: "Active Sessions", subtitle: "Manage your active sessions across all devices", theme: theme, onBack: goBack || onClose, elevation: "subtle" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, { style: styles.scrollView, contentContainerStyle: styles.scrollContainer, refreshControl: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.RefreshControl, { refreshing: refreshing, onRefresh: () => loadSessions(true), tintColor: primaryColor }), children: userSessions.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, { children: [lastRefreshed && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, { style: [styles.metaText, { color: isDarkTheme ? '#777' : '#777', marginBottom: 6 }], children: ["Last refreshed ", formatRelative(lastRefreshed.toISOString())] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.fullBleed, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.GroupedSection, { items: sessionItems, theme: theme }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: { height: 12 } }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.fullBleed, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.GroupedSection, { items: bulkItems, theme: theme }) })] }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.emptyState, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.emptyStateText, { color: isDarkTheme ? '#BBBBBB' : '#666666' }], children: "No active sessions found" }) }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: [styles.footer, { borderTopColor: borderColor }], children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { style: styles.closeButton, onPress: onClose, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.closeButtonText, { color: primaryColor }], children: "Close" }) }) })] }); }; const styles = _reactNative.StyleSheet.create({ container: { flex: 1 }, centerContent: { justifyContent: 'center', alignItems: 'center' }, scrollView: { flex: 1 }, scrollContainer: { padding: 20, paddingTop: 0 }, // Removed legacy session card & bulk action styles (now using GroupedSection) sessionActionsRow: { flexDirection: 'row', gap: 8, marginTop: 6 }, sessionPillButton: { paddingHorizontal: 14, paddingVertical: 6, borderRadius: 20, borderWidth: 1, flexDirection: 'row', alignItems: 'center' }, sessionPillText: { fontSize: 12, fontWeight: '600', letterSpacing: 0.3, textTransform: 'uppercase' }, currentBadgeText: { fontSize: 12, fontWeight: '600', paddingHorizontal: 10, paddingVertical: 4, backgroundColor: '#2E7D3215', borderRadius: 16, overflow: 'hidden', textTransform: 'uppercase', letterSpacing: 0.5 }, metaText: { fontSize: 12, textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: '600' }, fullBleed: { width: '100%', alignSelf: 'stretch' }, emptyState: { alignItems: 'center', paddingVertical: 40 }, emptyStateText: { fontSize: 16, fontStyle: 'italic' }, loadingText: { fontSize: 16, marginTop: 16 }, footer: { padding: 16, borderTopWidth: 1, alignItems: 'center' }, closeButton: { paddingVertical: 8, paddingHorizontal: 16 }, closeButtonText: { fontSize: 16, fontWeight: '600' } }); var _default = exports.default = SessionManagementScreen; //# sourceMappingURL=SessionManagementScreen.js.map