Contacts UI: address + phone inputs, search filter, address-only adds

The contacts manager in Settings was stuck at name+email inline only —
no address field, no phone input on add, no search to find anything in
a list of 100+ contacts.

UI:
- Add form gets phone and address inputs alongside name/email. The
  email-required gate becomes name-OR-email so address/phone-only
  entries are creatable.
- Edit form gets an address input, threaded into the PUT body.
- Search input above the list filters client-side by name / emails /
  phones / address (debounced 80ms). Count badge shows N/M when a
  filter is active.

Backend:
- /api/contacts/{uid} PUT now accepts address and routes it through
  _update_contact (which already supports it after the previous
  commit). Validation loosened: name OR email OR address.
- /api/contacts/add POST now accepts phone + address. Phone goes
  through an immediate _update_contact since _create_contact's
  signature only takes name+email+address.
This commit is contained in:
pewdiepie-archdaemon
2026-06-11 09:23:14 +09:00
parent f42cee8512
commit 2049eb7713
2 changed files with 109 additions and 45 deletions
+32 -12
View File
@@ -735,16 +735,35 @@ def setup_contacts_routes():
"""Add a new contact."""
name = (data.get("name") or "").strip()
email = (data.get("email") or "").strip()
if not email:
return {"success": False, "error": "Email required"}
# Check if already exists
contacts = _fetch_contacts()
for c in contacts:
if email.lower() in [e.lower() for e in c["emails"]]:
return {"success": True, "message": "Already exists", "contact": c}
phone = (data.get("phone") or "").strip()
address = (data.get("address") or "").strip()
if not email and not name:
return {"success": False, "error": "Name or email required"}
# Check if already exists by email
if email:
contacts = _fetch_contacts()
for c in contacts:
if email.lower() in [e.lower() for e in c["emails"]]:
return {"success": True, "message": "Already exists", "contact": c}
if not name:
name = email.split("@")[0]
ok = _create_contact(name, email)
ok = _create_contact(name, email, address)
# If a phone was provided, do an immediate update to thread it
# through (the simple _create_contact signature only takes name +
# email + address; phones happen via update).
if ok and phone:
try:
fresh = _fetch_contacts(force=True)
created = next((c for c in fresh if name == c.get("name") and (not email or email in c.get("emails", []))), None)
if created:
_update_contact(
created["uid"], name,
created.get("emails", []),
[phone],
address,
)
except Exception:
pass
return {"success": ok}
@router.post("/import")
@@ -820,7 +839,7 @@ def setup_contacts_routes():
# match PUT /{uid} with uid="config".
@router.put("/{uid}")
async def edit_contact(uid: str, data: dict, _admin: str = Depends(require_admin)):
"""Edit an existing contact — name / emails / phones."""
"""Edit an existing contact — name / emails / phones / address."""
name = (data.get("name") or "").strip()
emails = data.get("emails")
phones = data.get("phones")
@@ -828,11 +847,12 @@ def setup_contacts_routes():
emails = [data["email"]]
emails = [e.strip() for e in (emails or []) if e and e.strip()]
phones = [p.strip() for p in (phones or []) if p and p.strip()]
if not name and not emails:
return {"success": False, "error": "Name or email required"}
address = (data.get("address") or "").strip()
if not name and not emails and not address:
return {"success": False, "error": "Name, email, or address required"}
if not name and emails:
name = emails[0].split("@")[0]
ok = _update_contact(uid, name, emails, phones)
ok = _update_contact(uid, name, emails, phones, address)
return {"success": ok}
@router.delete("/{uid}")