feat(settings): harden privacy and sessions actions UX
Some checks are pending
CI / test (push) Has started running

This commit is contained in:
2026-03-08 20:33:16 +03:00
parent 4122882b7e
commit 586d3acc16
2 changed files with 39 additions and 11 deletions

View File

@@ -38,6 +38,8 @@ export function SettingsPanel({ open, onClose }: Props) {
const [privacyPrivateMessages, setPrivacyPrivateMessages] = useState<"everyone" | "contacts" | "nobody">("everyone");
const [sessions, setSessions] = useState<AuthSession[]>([]);
const [sessionsLoading, setSessionsLoading] = useState(false);
const [revokeAllLoading, setRevokeAllLoading] = useState(false);
const [revokingSessionIds, setRevokingSessionIds] = useState<string[]>([]);
const [twofaCode, setTwofaCode] = useState("");
const [twofaSecret, setTwofaSecret] = useState<string | null>(null);
const [twofaUrl, setTwofaUrl] = useState<string | null>(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) {
<div className="mb-2 flex items-center justify-between">
<p className="text-xs uppercase tracking-wide text-slate-400">Active Sessions</p>
<button
className="rounded bg-slate-700 px-2 py-1 text-xs"
className="rounded bg-slate-700 px-2 py-1 text-xs disabled:opacity-60"
onClick={async () => {
await revokeAllSessions();
setSessions([]);
logout();
onClose();
if (revokeAllLoading) {
return;
}
if (!window.confirm("Revoke all sessions and log out on this device?")) {
return;
}
setRevokeAllLoading(true);
try {
await revokeAllSessions();
setSessions([]);
logout();
onClose();
} catch {
showToast("Failed to revoke sessions");
} finally {
setRevokeAllLoading(false);
}
}}
disabled={revokeAllLoading}
type="button"
>
Revoke all
{revokeAllLoading ? "Revoking..." : "Revoke all"}
</button>
</div>
{sessionsLoading ? <p className="text-xs text-slate-400">Loading sessions...</p> : null}
@@ -691,14 +710,23 @@ export function SettingsPanel({ open, onClose }: Props) {
<p className="text-[11px] text-slate-500">{new Date(session.created_at).toLocaleString()}</p>
{session.token_type === "refresh" ? (
<button
className="mt-1 rounded bg-slate-700 px-2 py-1 text-[11px]"
className="mt-1 rounded bg-slate-700 px-2 py-1 text-[11px] disabled:opacity-60"
onClick={async () => {
await revokeSession(session.jti);
setSessions((prev) => prev.filter((item) => item.jti !== session.jti));
const token = session.jti;
setRevokingSessionIds((prev) => (prev.includes(token) ? prev : [...prev, token]));
try {
await revokeSession(session.jti);
setSessions((prev) => prev.filter((item) => item.jti !== session.jti));
} catch {
showToast("Failed to revoke session");
} finally {
setRevokingSessionIds((prev) => prev.filter((item) => item !== token));
}
}}
disabled={revokingSessionIds.includes(session.jti)}
type="button"
>
Revoke
{revokingSessionIds.includes(session.jti) ? "Revoking..." : "Revoke"}
</button>
) : (
<p className="mt-1 text-[11px] text-slate-500">Use Revoke all to invalidate active access token.</p>