101 lines
3.1 KiB
TypeScript
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>
|
|
);
|
|
}
|