veza/apps/web/src/features/auth/components/sessions-page/SessionsPage.tsx
2026-02-05 23:19:06 +01:00

101 lines
3.1 KiB
TypeScript

/**
* Sessions page — orchestration: useSessionsPage + Header, Error, RevokeAll, Content, dialogs.
*/
import { useMemo } from 'react';
import { parseUserAgent } from '../../utils/userAgentParser';
import { isPrivateIP } from '../../utils/ipLocation';
import { ConfirmationDialog } from '@/components/ui/confirmation-dialog';
import { useSessionsPage } from './useSessionsPage';
import { SessionsPageHeader } from './SessionsPageHeader';
import { SessionsPageErrorBanner } from './SessionsPageErrorBanner';
import { SessionsPageRevokeAllButton } from './SessionsPageRevokeAllButton';
import { SessionsPageContent } from './SessionsPageContent';
import { SessionsPageSkeleton } from './SessionsPageSkeleton';
import type { Session } from './types';
export interface SessionsPageProps {
/** For stories: override initial sessions (no fetch). */
initialSessions?: Session[];
/** For stories: force loading state. */
isLoading?: boolean;
}
export function SessionsPage(props?: SessionsPageProps) {
const options = props
? {
initialSessions: props.initialSessions,
isLoading: props.isLoading,
}
: undefined;
const {
sessions,
loading,
error,
revoking,
revokingAll,
sessionToRevoke,
showRevokeAllDialog,
handleRevokeClick,
revokeSession,
handleRevokeAllClick,
revokeAllOther,
closeRevokeDialog,
closeRevokeAllDialog,
} = useSessionsPage(options);
const sessionsWithDeviceInfo: Session[] = useMemo(
() =>
sessions.map((session) => ({
...session,
device_info: parseUserAgent(session.user_agent),
location_info: isPrivateIP(session.ip_address)
? { country: 'Local', region: 'Network', city: 'Private IP' }
: null,
})),
[sessions],
);
if (loading) {
return <SessionsPageSkeleton />;
}
return (
<div className="space-y-6">
<SessionsPageHeader />
{error && <SessionsPageErrorBanner message={error} />}
<SessionsPageRevokeAllButton
disabled={revokingAll || sessions.length <= 1}
loading={revokingAll}
onClick={handleRevokeAllClick}
/>
<SessionsPageContent
sessions={sessionsWithDeviceInfo}
revoking={revoking}
onRevokeClick={handleRevokeClick}
/>
<ConfirmationDialog
open={!!sessionToRevoke}
onClose={closeRevokeDialog}
onConfirm={revokeSession}
title="Revoke Session"
description="Are you sure you want to revoke this session? The user will be logged out from this device."
confirmLabel="Revoke"
cancelLabel="Cancel"
variant="destructive"
isLoading={!!revoking}
/>
<ConfirmationDialog
open={showRevokeAllDialog}
onClose={closeRevokeAllDialog}
onConfirm={revokeAllOther}
title="Revoke All Other Sessions"
description="Are you sure you want to revoke all other sessions? You will remain logged in on this device, but all other devices will be logged out."
confirmLabel="Revoke All"
cancelLabel="Cancel"
variant="destructive"
isLoading={revokingAll}
/>
</div>
);
}