diff --git a/app/web/account.py b/app/web/account.py
index 45d1026..9713a1a 100644
--- a/app/web/account.py
+++ b/app/web/account.py
@@ -7,7 +7,7 @@ from app.auth.csrf import ensure_csrf_token, validate_csrf
from app.db.repositories import ActivityRepository, SyncRunRepository, UserRepository
from app.security.credentials import CredentialCipher
from app.sync.manager import SyncAlreadyRunning
-from app.web.operations import _normalize_outcome
+from app.web.operations import _outcome_toast, _toast_html
from app.web.routes import templates
router = APIRouter()
@@ -92,11 +92,19 @@ async def account_sync(request: Request, csrf_token: str = Form(...)):
validate_csrf(request, csrf_token)
try:
outcome = await request.app.state.sync_manager.sync_user(user_id)
+ with request.app.state.session_factory() as session:
+ user = UserRepository(session).get(user_id)
+ label = user.name if user is not None else "Account"
+ message, level = _outcome_toast(outcome, label)
except SyncAlreadyRunning:
- return HTMLResponse("Sync already running for this user", status_code=409)
- return templates.TemplateResponse(
- request, "fragments/sync_result.html", {"outcomes": [_normalize_outcome(outcome)]}
+ with request.app.state.session_factory() as session:
+ user = UserRepository(session).get(user_id)
+ label = user.name if user is not None else "Account"
+ message, level = f"{label}: sync already running", "info"
+ status_html = (
+ templates.get_template("fragments/account_status.html").render(user=user) if user is not None else ""
)
+ return HTMLResponse(status_html + _toast_html(message, level))
@router.get("/account/edit", response_class=HTMLResponse)
diff --git a/app/web/templates/account/detail.html b/app/web/templates/account/detail.html
index 3a0c4c4..2ed3796 100644
--- a/app/web/templates/account/detail.html
+++ b/app/web/templates/account/detail.html
@@ -6,7 +6,8 @@
{{ user.name }}
Edit
-
@@ -17,19 +18,7 @@
-
- - Status
- - {{ user.health_state.value.replace("_", " ") }}
-
- - MyWhoosh state
- - {{ user.mywhoosh_state }}
-
- - Garmin state
- - {{ user.garmin_state }}
-
- - Action reason
- - {{ user.action_reason or "-" }}
-
+ {% include "fragments/account_status.html" %}
{% if user.action_reason == "mywhoosh_device_conflict" %}
diff --git a/app/web/templates/fragments/account_status.html b/app/web/templates/fragments/account_status.html
new file mode 100644
index 0000000..a78d008
--- /dev/null
+++ b/app/web/templates/fragments/account_status.html
@@ -0,0 +1,13 @@
+
+ - Status
+ - {{ user.health_state.value.replace("_", " ") }}
+
+ - MyWhoosh state
+ - {{ user.mywhoosh_state }}
+
+ - Garmin state
+ - {{ user.garmin_state }}
+
+ - Action reason
+ - {{ user.action_reason or "-" }}
+
diff --git a/tests/web/test_account_web.py b/tests/web/test_account_web.py
index 82c4f1c..a65a248 100644
--- a/tests/web/test_account_web.py
+++ b/tests/web/test_account_web.py
@@ -289,7 +289,7 @@ def test_account_sync_triggers_own_user_only(app, client: TestClient, fake_sync_
assert fake_sync_manager.user_calls == [user_id]
-def test_account_sync_reports_already_running(app, client: TestClient, fake_sync_manager) -> None:
+def test_account_sync_reports_already_running_as_toast(app, client: TestClient, fake_sync_manager) -> None:
create_user_via_admin(client)
account_login(client, email="max@mywhoosh.example", password="mw-secret")
app.state.sync_manager = fake_sync_manager
@@ -299,10 +299,25 @@ def test_account_sync_reports_already_running(app, client: TestClient, fake_sync
csrf = extract_csrf(page.text)
response = client.post("/account/sync", data={"csrf_token": csrf})
- assert response.status_code == 409
+ assert response.status_code == 200
assert "already running" in response.text.lower()
+def test_account_sync_updates_status_block_and_shows_toast(app, client: TestClient, fake_sync_manager) -> None:
+ create_user_via_admin(client)
+ account_login(client, email="max@mywhoosh.example", password="mw-secret")
+ app.state.sync_manager = fake_sync_manager
+
+ page = client.get("/account")
+ csrf = extract_csrf(page.text)
+ response = client.post("/account/sync", data={"csrf_token": csrf})
+
+ assert response.status_code == 200
+ assert 'id="account-status"' in response.text
+ assert 'hx-swap-oob="true"' in response.text
+ assert "0 imported, 0 failed" in response.text
+
+
def test_account_sync_requires_login(client: TestClient) -> None:
response = client.post("/account/sync", data={"csrf_token": "whatever"}, follow_redirects=False)
assert response.status_code == 303