Fix notification background polling, add first/last name sharing
Notifications: enable refetchIntervalInBackground on unread count query so notifications appear in background tabs without requiring a tab switch to trigger refetchOnWindowFocus. Name sharing: add share_first_name and share_last_name to the full sharing pipeline — migration 045, Settings model/schema, SHAREABLE_FIELDS, resolve_shared_profile, create_person_from_connection (now populates first_name + last_name + computed display name), SharingOverrideUpdate, frontend types and SettingsPage toggles. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
820ff46efa
commit
75fc3e3485
28
backend/alembic/versions/045_add_share_name_fields.py
Normal file
28
backend/alembic/versions/045_add_share_name_fields.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""Add share_first_name and share_last_name to settings.
|
||||
|
||||
Revision ID: 045
|
||||
Revises: 044
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "045"
|
||||
down_revision = "044"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"settings",
|
||||
sa.Column("share_first_name", sa.Boolean, nullable=False, server_default="false"),
|
||||
)
|
||||
op.add_column(
|
||||
"settings",
|
||||
sa.Column("share_last_name", sa.Boolean, nullable=False, server_default="false"),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("settings", "share_last_name")
|
||||
op.drop_column("settings", "share_first_name")
|
||||
@ -57,6 +57,8 @@ class Settings(Base):
|
||||
accept_connections: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
|
||||
|
||||
# Sharing defaults (what fields are shared with connections by default)
|
||||
share_first_name: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
|
||||
share_last_name: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
|
||||
share_preferred_name: Mapped[bool] = mapped_column(Boolean, default=True, server_default="true")
|
||||
share_email: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
|
||||
share_phone: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
|
||||
|
||||
@ -78,6 +78,8 @@ class CancelResponse(BaseModel):
|
||||
|
||||
class SharingOverrideUpdate(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
first_name: Optional[bool] = None
|
||||
last_name: Optional[bool] = None
|
||||
preferred_name: Optional[bool] = None
|
||||
email: Optional[bool] = None
|
||||
phone: Optional[bool] = None
|
||||
|
||||
@ -48,6 +48,8 @@ class SettingsUpdate(BaseModel):
|
||||
accept_connections: Optional[bool] = None
|
||||
|
||||
# Sharing defaults
|
||||
share_first_name: Optional[bool] = None
|
||||
share_last_name: Optional[bool] = None
|
||||
share_preferred_name: Optional[bool] = None
|
||||
share_email: Optional[bool] = None
|
||||
share_phone: Optional[bool] = None
|
||||
@ -185,6 +187,8 @@ class SettingsResponse(BaseModel):
|
||||
accept_connections: bool = False
|
||||
|
||||
# Sharing defaults
|
||||
share_first_name: bool = False
|
||||
share_last_name: bool = False
|
||||
share_preferred_name: bool = True
|
||||
share_email: bool = False
|
||||
share_phone: bool = False
|
||||
|
||||
@ -20,17 +20,19 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Single source of truth — only these fields can be shared via connections
|
||||
SHAREABLE_FIELDS = frozenset({
|
||||
"preferred_name", "email", "phone", "mobile",
|
||||
"first_name", "last_name", "preferred_name", "email", "phone", "mobile",
|
||||
"birthday", "address", "company", "job_title",
|
||||
})
|
||||
|
||||
# Maps shareable field names to their Settings model column names
|
||||
_SETTINGS_FIELD_MAP = {
|
||||
"first_name": None, # first_name comes from User model
|
||||
"last_name": None, # last_name comes from User model
|
||||
"preferred_name": "preferred_name",
|
||||
"email": None, # email comes from User model, not Settings
|
||||
"email": None, # email comes from User model
|
||||
"phone": "phone",
|
||||
"mobile": "mobile",
|
||||
"birthday": None, # birthday comes from User model (date_of_birth)
|
||||
"birthday": None, # birthday comes from User model (date_of_birth)
|
||||
"address": "address",
|
||||
"company": "company",
|
||||
"job_title": "job_title",
|
||||
@ -60,7 +62,11 @@ def resolve_shared_profile(
|
||||
continue
|
||||
|
||||
# Resolve the actual value
|
||||
if field == "preferred_name":
|
||||
if field == "first_name":
|
||||
result[field] = user.first_name
|
||||
elif field == "last_name":
|
||||
result[field] = user.last_name
|
||||
elif field == "preferred_name":
|
||||
result[field] = settings.preferred_name
|
||||
elif field == "email":
|
||||
result[field] = user.email
|
||||
@ -79,8 +85,9 @@ def create_person_from_connection(
|
||||
shared_profile: dict,
|
||||
) -> Person:
|
||||
"""Create a Person record for a new connection. Does NOT add to session — caller does."""
|
||||
# Use shared preferred_name for display, fall back to umbral_name
|
||||
first_name = shared_profile.get("preferred_name") or connected_user.umbral_name
|
||||
# Use shared first_name, fall back to preferred_name, then umbral_name
|
||||
first_name = shared_profile.get("first_name") or shared_profile.get("preferred_name") or connected_user.umbral_name
|
||||
last_name = shared_profile.get("last_name")
|
||||
email = shared_profile.get("email")
|
||||
phone = shared_profile.get("phone")
|
||||
mobile = shared_profile.get("mobile")
|
||||
@ -97,12 +104,14 @@ def create_person_from_connection(
|
||||
pass
|
||||
|
||||
# Compute display name
|
||||
display_name = first_name or connected_user.umbral_name
|
||||
full = ((first_name or '') + ' ' + (last_name or '')).strip()
|
||||
display_name = full or connected_user.umbral_name
|
||||
|
||||
return Person(
|
||||
user_id=owner_user_id,
|
||||
name=display_name,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
email=email,
|
||||
phone=phone,
|
||||
mobile=mobile,
|
||||
|
||||
@ -66,6 +66,8 @@ export default function SettingsPage() {
|
||||
|
||||
// Social settings
|
||||
const [acceptConnections, setAcceptConnections] = useState(settings?.accept_connections ?? false);
|
||||
const [shareFirstName, setShareFirstName] = useState(settings?.share_first_name ?? false);
|
||||
const [shareLastName, setShareLastName] = useState(settings?.share_last_name ?? false);
|
||||
const [sharePreferredName, setSharePreferredName] = useState(settings?.share_preferred_name ?? true);
|
||||
const [shareEmail, setShareEmail] = useState(settings?.share_email ?? false);
|
||||
const [sharePhone, setSharePhone] = useState(settings?.share_phone ?? false);
|
||||
@ -116,6 +118,8 @@ export default function SettingsPage() {
|
||||
setSettingsCompany(settings.company ?? '');
|
||||
setSettingsJobTitle(settings.job_title ?? '');
|
||||
setAcceptConnections(settings.accept_connections);
|
||||
setShareFirstName(settings.share_first_name);
|
||||
setShareLastName(settings.share_last_name);
|
||||
setSharePreferredName(settings.share_preferred_name);
|
||||
setShareEmail(settings.share_email);
|
||||
setSharePhone(settings.share_phone);
|
||||
@ -788,6 +792,8 @@ export default function SettingsPage() {
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{[
|
||||
{ field: 'share_first_name', label: 'First Name', state: shareFirstName, setter: setShareFirstName },
|
||||
{ field: 'share_last_name', label: 'Last Name', state: shareLastName, setter: setShareLastName },
|
||||
{ field: 'share_preferred_name', label: 'Preferred Name', state: sharePreferredName, setter: setSharePreferredName },
|
||||
{ field: 'share_email', label: 'Email', state: shareEmail, setter: setShareEmail },
|
||||
{ field: 'share_phone', label: 'Phone', state: sharePhone, setter: setSharePhone },
|
||||
|
||||
@ -48,7 +48,8 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
|
||||
const { data } = await api.get<{ count: number }>('/notifications/unread-count');
|
||||
return data.count;
|
||||
},
|
||||
refetchInterval: () => (visibleRef.current ? 15_000 : false),
|
||||
refetchInterval: 15_000,
|
||||
refetchIntervalInBackground: true,
|
||||
staleTime: 10_000,
|
||||
});
|
||||
|
||||
|
||||
@ -32,6 +32,8 @@ export interface Settings {
|
||||
// Social settings
|
||||
accept_connections: boolean;
|
||||
// Sharing defaults
|
||||
share_first_name: boolean;
|
||||
share_last_name: boolean;
|
||||
share_preferred_name: boolean;
|
||||
share_email: boolean;
|
||||
share_phone: boolean;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user