diff --git a/docs/core-checklist-status.md b/docs/core-checklist-status.md index ade6468..a7158fe 100644 --- a/docs/core-checklist-status.md +++ b/docs/core-checklist-status.md @@ -38,7 +38,7 @@ Legend: 29. Archive - `DONE` 30. Blacklist - `DONE` 31. Privacy - `PARTIAL` (avatar/last-seen/group-invites + PM policy `everyone|contacts|nobody`; group-invite `nobody` is available in API and web settings; integration tests cover PM policy matrix (`everyone/contacts/nobody`), group-invite policy matrix (`everyone/contacts/nobody`), and private chat counterpart visibility for `nobody/contacts`, remaining UX/matrix hardening) -32. Security - `PARTIAL` (sessions + revoke + 2FA base + access-session visibility; integration tests cover single-session revoke and revoke-all invalidation/force-disconnect; 2FA setup now blocked after enable to prevent secret re-issuance; one-time recovery codes added; UX polish ongoing) +32. Security - `PARTIAL` (sessions + revoke + 2FA base + access-session visibility; integration tests cover single-session revoke and revoke-all invalidation/force-disconnect; 2FA setup now blocked after enable to prevent secret re-issuance; one-time recovery codes added; web settings now has safer revoke UX with confirmation/loading/error feedback) 33. Realtime Events - `DONE` (connect/disconnect/send/receive/typing/read/delivered/online/offline + chat/message updates + chat_deleted) 34. Sync - `PARTIAL` (cross-device via backend state + realtime; reconciliation improved for loaded chats/messages, chat-info panel hot-refreshes on `chat_updated`, delete/leave updates realtime subscriptions, full-chat delete emits `chat_deleted`) 35. Additional - `PARTIAL` (drafts/link preview partial/autoload media basic) diff --git a/web/src/components/SettingsPanel.tsx b/web/src/components/SettingsPanel.tsx index f48277d..20f6c11 100644 --- a/web/src/components/SettingsPanel.tsx +++ b/web/src/components/SettingsPanel.tsx @@ -38,6 +38,8 @@ export function SettingsPanel({ open, onClose }: Props) { const [privacyPrivateMessages, setPrivacyPrivateMessages] = useState<"everyone" | "contacts" | "nobody">("everyone"); const [sessions, setSessions] = useState([]); const [sessionsLoading, setSessionsLoading] = useState(false); + const [revokeAllLoading, setRevokeAllLoading] = useState(false); + const [revokingSessionIds, setRevokingSessionIds] = useState([]); const [twofaCode, setTwofaCode] = useState(""); const [twofaSecret, setTwofaSecret] = useState(null); const [twofaUrl, setTwofaUrl] = useState(null); @@ -521,6 +523,9 @@ export function SettingsPanel({ open, onClose }: Props) { privacy_group_invites: privacyGroupInvites, }); useAuthStore.setState({ me: updated as AuthUser }); + showToast("Privacy settings saved"); + } catch { + showToast("Failed to save privacy settings"); } finally { setSavingPrivacy(false); } @@ -665,16 +670,30 @@ export function SettingsPanel({ open, onClose }: Props) {

Active Sessions

{sessionsLoading ?

Loading sessions...

: null} @@ -691,14 +710,23 @@ export function SettingsPanel({ open, onClose }: Props) {

{new Date(session.created_at).toLocaleString()}

{session.token_type === "refresh" ? ( ) : (

Use Revoke all to invalidate active access token.