2026-07-03 11:23:14 -04:00
import React , { useCallback } from 'react' ;
import { Box , Button , color , Spinner , Text } from 'folds' ;
import { MatrixError } from 'matrix-js-sdk' ;
import { SequenceCard } from '../../../components/sequence-card' ;
import { SequenceCardStyle } from '../../room-settings/styles.css' ;
import { SettingTile } from '../../../components/setting-tile' ;
import { useMatrixClient } from '../../../hooks/useMatrixClient' ;
import { useRoom } from '../../../hooks/useRoom' ;
import { StateEvent } from '../../../../types/matrix/room' ;
2026-07-07 23:32:50 -04:00
import { sendStateEvent } from '../../../utils/room' ;
2026-07-03 11:23:14 -04:00
import { AsyncStatus , useAsyncCallback } from '../../../hooks/useAsyncCallback' ;
import { useStateEvent } from '../../../hooks/useStateEvent' ;
import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions' ;
import { RetentionContent , RETENTION_PRESETS } from '../../../utils/retention' ;
type RoomRetentionProps = {
permissions : RoomPermissionsAPI ;
};
export function RoomRetention ({ permissions } : RoomRetentionProps ) {
const mx = useMatrixClient ();
const room = useRoom ();
const canEdit = permissions . stateEvent ( StateEvent . RoomRetention , mx . getSafeUserId ());
const event = useStateEvent ( room , StateEvent . RoomRetention );
const currentMs = event ? . getContent < RetentionContent >(). max_lifetime ?? 0 ;
const [ submitState , submit ] = useAsyncCallback (
useCallback (
async ( ms : number ) => {
const content : RetentionContent = ms > 0 ? { max_lifetime : ms } : {};
// Lotus custom-state convention: cast the type key (RoomRetention isn't a
// typed key in the SDK's StateEvents map).
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2026-07-07 23:32:50 -04:00
await sendStateEvent ( mx , room . roomId , StateEvent . RoomRetention , content );
2026-07-03 11:23:14 -04:00
},
[ mx , room . roomId ],
),
);
const submitting = submitState . status === AsyncStatus . Loading ;
return (
< SequenceCard
className = { SequenceCardStyle }
variant = "SurfaceVariant"
direction = "Column"
gap = "400"
>
< SettingTile
title = "Message Retention"
description = "Messages older than this window disappear from the timeline. Each member can opt in to permanently delete their own expired messages in Settings → General; full server-side deletion also requires homeserver retention to be configured."
>
< Box gap = "200" alignItems = "Center" style = {{ flexWrap : 'wrap' }}>
{ RETENTION_PRESETS . map (( preset ) => {
const active = currentMs === preset . ms ;
return (
< Button
key = { preset . label }
type = "button"
size = "300"
variant = { active ? 'Primary' : 'Secondary' }
fill = { active ? 'Solid' : 'Soft' }
radii = "300"
disabled = { ! canEdit || submitting }
onClick = {() => submit ( preset . ms )}
>
< Text size = "B300" >{ preset . label }</ Text >
</ Button >
);
})}
{ submitting && < Spinner size = "100" variant = "Secondary" />}
</ Box >
{ submitState . status === AsyncStatus . Error && (
< Text style = {{ color : color.Critical.Main }} size = "T200" >
{( submitState . error as MatrixError ). message }
</ Text >
)}
</ SettingTile >
</ SequenceCard >
);
}