feat(privacy): support nobody option for group invites
Some checks are pending
CI / test (push) Has started running
Some checks are pending
CI / test (push) Has started running
This commit is contained in:
@@ -5,7 +5,7 @@ from typing import Literal
|
|||||||
|
|
||||||
|
|
||||||
PrivacyLevel = Literal["everyone", "contacts", "nobody"]
|
PrivacyLevel = Literal["everyone", "contacts", "nobody"]
|
||||||
GroupInvitePrivacyLevel = Literal["everyone", "contacts"]
|
GroupInvitePrivacyLevel = Literal["everyone", "contacts", "nobody"]
|
||||||
PrivateMessagesPrivacyLevel = Literal["everyone", "contacts", "nobody"]
|
PrivateMessagesPrivacyLevel = Literal["everyone", "contacts", "nobody"]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ Server behavior: when a user disconnects, active typing/recording indicators are
|
|||||||
|
|
||||||
All fields are optional.
|
All fields are optional.
|
||||||
`privacy_private_messages`: `everyone | contacts | nobody`.
|
`privacy_private_messages`: `everyone | contacts | nobody`.
|
||||||
|
`privacy_group_invites`: `everyone | contacts | nobody`.
|
||||||
|
|
||||||
## 3.3 Chats
|
## 3.3 Chats
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ Legend:
|
|||||||
28. Notifications - `PARTIAL` (browser notifications + mute/settings; no mobile push infra)
|
28. Notifications - `PARTIAL` (browser notifications + mute/settings; no mobile push infra)
|
||||||
29. Archive - `DONE`
|
29. Archive - `DONE`
|
||||||
30. Blacklist - `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)
|
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)
|
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`)
|
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`)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ interface UserProfileUpdatePayload {
|
|||||||
privacy_private_messages?: "everyone" | "contacts" | "nobody";
|
privacy_private_messages?: "everyone" | "contacts" | "nobody";
|
||||||
privacy_last_seen?: "everyone" | "contacts" | "nobody";
|
privacy_last_seen?: "everyone" | "contacts" | "nobody";
|
||||||
privacy_avatar?: "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> {
|
export async function updateMyProfile(payload: UserProfileUpdatePayload): Promise<AuthUser> {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export interface AuthUser {
|
|||||||
privacy_private_messages?: "everyone" | "contacts" | "nobody";
|
privacy_private_messages?: "everyone" | "contacts" | "nobody";
|
||||||
privacy_last_seen?: "everyone" | "contacts" | "nobody";
|
privacy_last_seen?: "everyone" | "contacts" | "nobody";
|
||||||
privacy_avatar?: "everyone" | "contacts" | "nobody";
|
privacy_avatar?: "everyone" | "contacts" | "nobody";
|
||||||
privacy_group_invites?: "everyone" | "contacts";
|
privacy_group_invites?: "everyone" | "contacts" | "nobody";
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function SettingsPanel({ open, onClose }: Props) {
|
|||||||
const [recoveryCodes, setRecoveryCodes] = useState<string[]>([]);
|
const [recoveryCodes, setRecoveryCodes] = useState<string[]>([]);
|
||||||
const [privacyLastSeen, setPrivacyLastSeen] = useState<"everyone" | "contacts" | "nobody">("everyone");
|
const [privacyLastSeen, setPrivacyLastSeen] = useState<"everyone" | "contacts" | "nobody">("everyone");
|
||||||
const [privacyAvatar, setPrivacyAvatar] = 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 [notificationItems, setNotificationItems] = useState<NotificationItem[]>([]);
|
||||||
const [notificationItemsLoading, setNotificationItemsLoading] = useState(false);
|
const [notificationItemsLoading, setNotificationItemsLoading] = useState(false);
|
||||||
const [profileDraft, setProfileDraft] = useState({
|
const [profileDraft, setProfileDraft] = useState({
|
||||||
@@ -501,10 +501,11 @@ export function SettingsPanel({ open, onClose }: Props) {
|
|||||||
<select
|
<select
|
||||||
className="w-full rounded bg-slate-800 px-2 py-1 text-xs"
|
className="w-full rounded bg-slate-800 px-2 py-1 text-xs"
|
||||||
value={privacyGroupInvites}
|
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="everyone">Everybody</option>
|
||||||
<option value="contacts">My contacts</option>
|
<option value="contacts">My contacts</option>
|
||||||
|
<option value="nobody">Nobody</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user