From b086be3defafaa33e333ee060a948ad939c907cc Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Wed, 27 May 2026 13:10:46 -0400 Subject: [PATCH] feat: auto-clear status after configurable duration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an 'Auto-clear after' dropdown to the Status Message settings tile with options: Never / 30 min / 1 hr / 4 hr / 8 hr / Until midnight / 1 day / 7 days. How it works: - On save, stores the expiry timestamp in localStorage keyed by userId (lotus-status-expiry-) and sets expiryTs state - A single useEffect on expiryTs drives the timer — re-saving cancels the previous timer cleanly via useEffect cleanup - On mount, reads stored expiry from localStorage so auto-clear survives page reloads (fires immediately if already expired) - Manual Clear Status also removes the stored expiry and cancels any active timer Co-Authored-By: Claude Sonnet 4.6 --- src/app/features/settings/account/Profile.tsx | 96 +++++++++++++++++-- 1 file changed, 90 insertions(+), 6 deletions(-) diff --git a/src/app/features/settings/account/Profile.tsx b/src/app/features/settings/account/Profile.tsx index 4a855ee4f..5ff63c7c1 100644 --- a/src/app/features/settings/account/Profile.tsx +++ b/src/app/features/settings/account/Profile.tsx @@ -319,19 +319,68 @@ function ProfileDisplayName({ profile, userId }: ProfileProps) { ); } +const STATUS_EXPIRY_KEY = (id: string) => `lotus-status-expiry-${id}`; + +const CLEAR_AFTER_OPTIONS = [ + { label: 'Never', value: '0' }, + { label: '30 minutes', value: String(30 * 60 * 1000) }, + { label: '1 hour', value: String(60 * 60 * 1000) }, + { label: '4 hours', value: String(4 * 60 * 60 * 1000) }, + { label: '8 hours', value: String(8 * 60 * 60 * 1000) }, + { label: 'Until midnight', value: 'today' }, + { label: '1 day', value: String(24 * 60 * 60 * 1000) }, + { label: '7 days', value: String(7 * 24 * 60 * 60 * 1000) }, +]; + +function getMsFromOption(value: string): number { + if (value === '0') return 0; + if (value === 'today') { + const eod = new Date(); + eod.setHours(23, 59, 59, 999); + return eod.getTime() - Date.now(); + } + return parseInt(value, 10); +} + function ProfileStatus() { const mx = useMatrixClient(); const userId = mx.getUserId()!; const presence = useUserPresence(userId); const [statusMsg, setStatusMsg] = useState(presence?.status ?? ''); + const [clearAfter, setClearAfter] = useState('0'); const [emojiAnchor, setEmojiAnchor] = useState(); const inputRef = useRef(null); + // Initialise expiry from localStorage so timer survives page reload + const [expiryTs, setExpiryTs] = useState(() => { + const stored = localStorage.getItem(STATUS_EXPIRY_KEY(userId)); + return stored ? parseInt(stored, 10) : 0; + }); + + // Sync input when another device changes the status useEffect(() => { setStatusMsg(presence?.status ?? ''); }, [presence?.status]); + // Drive the auto-clear timer off expiryTs so re-saving cancels the old timer + useEffect(() => { + if (!expiryTs) return undefined; + const remaining = expiryTs - Date.now(); + if (remaining <= 0) { + localStorage.removeItem(STATUS_EXPIRY_KEY(userId)); + setExpiryTs(0); + mx.setPresence({ presence: 'online', status_msg: '' }); + return undefined; + } + const timer = window.setTimeout(() => { + localStorage.removeItem(STATUS_EXPIRY_KEY(userId)); + setExpiryTs(0); + mx.setPresence({ presence: 'online', status_msg: '' }); + }, remaining); + return () => clearTimeout(timer); + }, [expiryTs, userId, mx]); + const [saveState, saveStatus] = useAsyncCallback( useCallback( (msg: string) => @@ -352,7 +401,6 @@ function ProfileStatus() { const end = input.selectionEnd ?? statusMsg.length; const next = statusMsg.slice(0, start) + unicode + statusMsg.slice(end); setStatusMsg(next); - // restore cursor after emoji insertion requestAnimationFrame(() => { input.focus(); input.setSelectionRange(start + unicode.length, start + unicode.length); @@ -372,15 +420,25 @@ function ProfileStatus() { const handleSubmit: FormEventHandler = (evt) => { evt.preventDefault(); if (saving) return; - saveStatus(statusMsg.trim()); + const msg = statusMsg.trim(); + saveStatus(msg); + + const delayMs = getMsFromOption(clearAfter); + if (msg && delayMs > 0) { + const ts = Date.now() + delayMs; + localStorage.setItem(STATUS_EXPIRY_KEY(userId), String(ts)); + setExpiryTs(ts); + } else { + localStorage.removeItem(STATUS_EXPIRY_KEY(userId)); + setExpiryTs(0); + } }; const handleClear = () => { setStatusMsg(''); - mx.setPresence({ - presence: 'online', - status_msg: '', - }); + localStorage.removeItem(STATUS_EXPIRY_KEY(userId)); + setExpiryTs(0); + mx.setPresence({ presence: 'online', status_msg: '' }); }; const hasChanges = statusMsg !== (presence?.status ?? ''); @@ -460,6 +518,32 @@ function ProfileStatus() { Save + + + Auto-clear after: + + + {(presence?.status || statusMsg) && (