Fix QA review findings: per-card responding state, preserve data on detach
C-01: ConnectionRequestCard now uses local isResponding state instead of shared hook boolean, so accepting one card doesn't disable all others. C-03: detach_umbral_contact no longer wipes person data (name, email, phone, etc.) when a connection is severed. The person becomes a standard contact with all data preserved, preventing data loss for pre-existing contacts that were linked to connections. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
aeb30afbce
commit
3fe344c3a0
@ -126,19 +126,15 @@ def create_person_from_connection(
|
|||||||
|
|
||||||
|
|
||||||
async def detach_umbral_contact(person: Person) -> None:
|
async def detach_umbral_contact(person: Person) -> None:
|
||||||
"""Convert an umbral contact back to a standard contact. Does NOT commit."""
|
"""Convert an umbral contact back to a standard contact. Does NOT commit.
|
||||||
|
|
||||||
|
Preserves all person data (name, email, phone, etc.) so the user does not
|
||||||
|
lose contact information when a connection is severed. Only unlinks the
|
||||||
|
umbral association — the person becomes a standard contact.
|
||||||
|
"""
|
||||||
person.linked_user_id = None
|
person.linked_user_id = None
|
||||||
person.is_umbral_contact = False
|
person.is_umbral_contact = False
|
||||||
person.category = None
|
person.category = None
|
||||||
# Clear all shareable fields — they were populated from the connection
|
|
||||||
# Preserve first_name from existing name so the contact is not left blank
|
|
||||||
fallback_name = person.first_name or person.name or None
|
|
||||||
for field in SHAREABLE_FIELDS:
|
|
||||||
if hasattr(person, field):
|
|
||||||
setattr(person, field, None)
|
|
||||||
person.first_name = fallback_name
|
|
||||||
# Recompute display name
|
|
||||||
person.name = fallback_name or "Removed Contact"
|
|
||||||
|
|
||||||
def extract_ntfy_config(settings: Settings) -> dict | None:
|
def extract_ntfy_config(settings: Settings) -> dict | None:
|
||||||
"""Extract ntfy config values into a plain dict safe for use after session close."""
|
"""Extract ntfy config values into a plain dict safe for use after session close."""
|
||||||
|
|||||||
@ -15,7 +15,8 @@ interface ConnectionRequestCardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ConnectionRequestCard({ request, direction }: ConnectionRequestCardProps) {
|
export default function ConnectionRequestCard({ request, direction }: ConnectionRequestCardProps) {
|
||||||
const { respond, isResponding, cancelRequest, isCancelling } = useConnections();
|
const { respond, cancelRequest, isCancelling } = useConnections();
|
||||||
|
const [isResponding, setIsResponding] = useState(false);
|
||||||
const [resolved, setResolved] = useState(false);
|
const [resolved, setResolved] = useState(false);
|
||||||
|
|
||||||
// Clean up invisible DOM element after fade-out transition
|
// Clean up invisible DOM element after fade-out transition
|
||||||
@ -29,6 +30,7 @@ export default function ConnectionRequestCard({ request, direction }: Connection
|
|||||||
if (hidden) return null;
|
if (hidden) return null;
|
||||||
|
|
||||||
const handleRespond = async (action: 'accept' | 'reject') => {
|
const handleRespond = async (action: 'accept' | 'reject') => {
|
||||||
|
setIsResponding(true);
|
||||||
try {
|
try {
|
||||||
await respond({ requestId: request.id, action });
|
await respond({ requestId: request.id, action });
|
||||||
setResolved(true);
|
setResolved(true);
|
||||||
@ -41,6 +43,8 @@ export default function ConnectionRequestCard({ request, direction }: Connection
|
|||||||
} else {
|
} else {
|
||||||
toast.error(getErrorMessage(err, 'Failed to respond'));
|
toast.error(getErrorMessage(err, 'Failed to respond'));
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
setIsResponding(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user