Files
emaui/libs/auth/src/lib/components/ActiveSessions/columns.tsx

125 lines
3.8 KiB
TypeScript

import type { Dispatch, SetStateAction } from 'react';
import { ActionIcon, Badge, Checkbox, Group, Text, Tooltip } from '@mantine/core';
import { IconLogout } from '@tabler/icons-react';
import type { TFunction } from 'i18next';
import type { AdvancedColumn } from '@ema-platform/ui';
import type { MySession } from '../../hooks/useSessions';
interface Opts {
t: TFunction;
sessions: MySession[];
selected: string[];
setSelected: Dispatch<SetStateAction<string[]>>;
/** Undefined when the token carries no session claim — then no row is "this device". */
currentId?: string;
showDate: (value: string) => string;
onRevoke: (session: MySession) => void;
}
export function sessionColumns({
t,
sessions,
selected,
setSelected,
currentId,
showDate,
onRevoke,
}: Opts): AdvancedColumn<MySession>[] {
// The current session is never selectable, so "all" means "all the others".
const selectable = sessions.filter((s) => s.id !== currentId);
const allSelected = selectable.length > 0 && selectable.every((s) => selected.includes(s.id));
return [
{
header: (
<Checkbox
aria-label={t('profile.sessions.selectAll')}
checked={allSelected}
indeterminate={selected.length > 0 && !allSelected}
disabled={selectable.length === 0}
onChange={() => setSelected(allSelected ? [] : selectable.map((s) => s.id))}
/>
),
label: t('profile.sessions.select'),
size: 40,
cell: ({ row }) => {
const isCurrent = row.original.id === currentId;
return (
<Checkbox
aria-label={t('profile.sessions.selectRow', { device: row.original.device })}
checked={selected.includes(row.original.id)}
disabled={isCurrent}
onChange={(e) => {
const checked = e.currentTarget.checked;
setSelected((prev) =>
checked
? [...prev, row.original.id]
: prev.filter((id) => id !== row.original.id),
);
}}
/>
);
},
},
{
header: t('profile.sessions.columns.device'),
cell: ({ row }) => (
<Group gap="xs" wrap="nowrap">
<Text size="sm" fw={500}>
{row.original.device || '—'}
</Text>
{row.original.id === currentId && (
<Badge variant="light" color="emaTeal" size="sm">
{t('profile.sessions.thisDevice')}
</Badge>
)}
</Group>
),
},
{
header: t('profile.sessions.columns.signedIn'),
cell: ({ row }) => <Text size="sm">{showDate(row.original.createdAt)}</Text>,
},
{
header: t('profile.sessions.columns.expires'),
cell: ({ row }) => <Text size="sm">{showDate(row.original.expiryTime)}</Text>,
},
{
header: t('profile.sessions.columns.status'),
cell: ({ row }) => (
<Badge variant="light" size="sm" color={row.original.status === 'ACTIVE' ? 'green' : 'gray'}>
{row.original.status}
</Badge>
),
},
{
header: t('profile.sessions.columns.actions'),
size: 70,
align: 'center',
cell: ({ row }) => {
const isCurrent = row.original.id === currentId;
return (
<Tooltip
label={
isCurrent ? t('profile.sessions.cannotRevokeCurrent') : t('profile.sessions.revoke')
}
>
<div>
<ActionIcon
variant="subtle"
color="red"
size="sm"
disabled={isCurrent}
aria-label={t('profile.sessions.revoke')}
onClick={() => onRevoke(row.original)}
>
<IconLogout size={14} />
</ActionIcon>
</div>
</Tooltip>
);
},
},
];
}