feat(privacy): support nobody option for group invites
Some checks are pending
CI / test (push) Has started running

This commit is contained in:
2026-03-08 20:32:29 +03:00
parent 362098b954
commit 4122882b7e
6 changed files with 8 additions and 6 deletions

View File

@@ -5,7 +5,7 @@ from typing import Literal
PrivacyLevel = Literal["everyone", "contacts", "nobody"]
GroupInvitePrivacyLevel = Literal["everyone", "contacts"]
GroupInvitePrivacyLevel = Literal["everyone", "contacts", "nobody"]
PrivateMessagesPrivacyLevel = Literal["everyone", "contacts", "nobody"]

View File

@@ -211,6 +211,7 @@ Server behavior: when a user disconnects, active typing/recording indicators are
All fields are optional.
`privacy_private_messages`: `everyone | contacts | nobody`.
`privacy_group_invites`: `everyone | contacts | nobody`.
## 3.3 Chats

View File

@@ -37,7 +37,7 @@ Legend:
28. Notifications - `PARTIAL` (browser notifications + mute/settings; no mobile push infra)
29. Archive - `DONE`
30. Blacklist - `DONE`
31. Privacy - `PARTIAL` (avatar/last-seen/group-invites + PM policy `everyone|contacts|nobody`; 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)
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)
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`)

View File

@@ -17,7 +17,7 @@ interface UserProfileUpdatePayload {
privacy_private_messages?: "everyone" | "contacts" | "nobody";
privacy_last_seen?: "everyone" | "contacts" | "nobody";
privacy_avatar?: "everyone" | "contacts" | "nobody";
privacy_group_invites?: "everyone" | "contacts";
privacy_group_invites?: "everyone" | "contacts" | "nobody";
}
export async function updateMyProfile(payload: UserProfileUpdatePayload): Promise<AuthUser> {

View File

@@ -86,7 +86,7 @@ export interface AuthUser {
privacy_private_messages?: "everyone" | "contacts" | "nobody";
privacy_last_seen?: "everyone" | "contacts" | "nobody";
privacy_avatar?: "everyone" | "contacts" | "nobody";
privacy_group_invites?: "everyone" | "contacts";
privacy_group_invites?: "everyone" | "contacts" | "nobody";
created_at: string;
updated_at: string;
}

View File

@@ -47,7 +47,7 @@ export function SettingsPanel({ open, onClose }: Props) {
const [recoveryCodes, setRecoveryCodes] = useState<string[]>([]);
const [privacyLastSeen, setPrivacyLastSeen] = useState<"everyone" | "contacts" | "nobody">("everyone");
const [privacyAvatar, setPrivacyAvatar] = useState<"everyone" | "contacts" | "nobody">("everyone");
const [privacyGroupInvites, setPrivacyGroupInvites] = useState<"everyone" | "contacts">("everyone");
const [privacyGroupInvites, setPrivacyGroupInvites] = useState<"everyone" | "contacts" | "nobody">("everyone");
const [notificationItems, setNotificationItems] = useState<NotificationItem[]>([]);
const [notificationItemsLoading, setNotificationItemsLoading] = useState(false);
const [profileDraft, setProfileDraft] = useState({
@@ -501,10 +501,11 @@ export function SettingsPanel({ open, onClose }: Props) {
<select
className="w-full rounded bg-slate-800 px-2 py-1 text-xs"
value={privacyGroupInvites}
onChange={(e) => setPrivacyGroupInvites(e.target.value as "everyone" | "contacts")}
onChange={(e) => setPrivacyGroupInvites(e.target.value as "everyone" | "contacts" | "nobody")}
>
<option value="everyone">Everybody</option>
<option value="contacts">My contacts</option>
<option value="nobody">Nobody</option>
</select>
</div>
<button