diff --git a/app/api/onboarding.py b/app/api/onboarding.py index ffdf27ea..9b0c8f20 100644 --- a/app/api/onboarding.py +++ b/app/api/onboarding.py @@ -208,8 +208,13 @@ def save_storage(request: Request, body: StorageBody, db: DbSession) -> dict[str @router.post("/complete", summary="Mark onboarding as completed") -def complete_onboarding(request: Request, db: DbSession) -> dict[str, bool]: - """Set onboarding_completed=True and record the completion timestamp.""" +def complete_onboarding(request: Request, db: DbSession) -> dict[str, Any]: + """Set onboarding_completed=True, record the completion timestamp, and return the post-onboarding redirect URL. + + The redirect URL is read from ``request.session["post_onboarding_redirect"]`` (stored by + ``oauth_callback`` when it reroutes a first-time user to the wizard) and defaults to + ``/upload`` when the session key is absent. + """ user_id = _get_current_user_id(request) profile = _get_or_create_profile(db, user_id) profile.onboarding_completed = True @@ -221,5 +226,6 @@ def complete_onboarding(request: Request, db: DbSession) -> dict[str, bool]: db.rollback() raise - logger.info("Onboarding: completed for user %s", user_id) - return {"success": True} + redirect_url = request.session.pop("post_onboarding_redirect", "/upload") + logger.info("Onboarding: completed for user %s, redirecting to %s", user_id, redirect_url) + return {"success": True, "redirect_url": redirect_url} diff --git a/app/auth.py b/app/auth.py index 707f4279..b5d7d6dd 100644 --- a/app/auth.py +++ b/app/auth.py @@ -165,7 +165,7 @@ async def oauth_callback(request: Request, db: Session = Depends(get_db)): _ensure_user_profile(db, user_data) # Log the successful authentication - logger.info(f"[SECURITY] OAUTH_LOGIN_SUCCESS user={user_data.get('email', 'unknown')} admin={is_admin}") + logger.info("[SECURITY] OAUTH_LOGIN_SUCCESS user=%s admin=%s", user_data.get("email", "unknown"), is_admin) # Redirect first-time users to onboarding user_id = ( diff --git a/app/views/onboarding.py b/app/views/onboarding.py index 8f6a85fa..3e551dbf 100644 --- a/app/views/onboarding.py +++ b/app/views/onboarding.py @@ -5,6 +5,7 @@ import logging from fastapi import Depends, Request from sqlalchemy.orm import Session +from app.config import Settings from app.config import settings as _settings from app.models import UserProfile from app.utils.subscription import get_all_tiers @@ -29,7 +30,7 @@ _DESTINATION_META: list[dict] = [ ] -def _get_configured_destinations(cfg) -> list[dict]: +def _get_configured_destinations(cfg: Settings) -> list[dict]: """Return which storage providers are fully configured in the current settings. Each entry is a dict with ``id``, ``name``, and ``icon`` keys. diff --git a/frontend/templates/onboarding.html b/frontend/templates/onboarding.html index d3368538..70c5531e 100644 --- a/frontend/templates/onboarding.html +++ b/frontend/templates/onboarding.html @@ -151,12 +151,12 @@

Choose Your Plan

Start free, upgrade when you're ready

-
+
-
+
Monthly -
@@ -374,6 +378,7 @@ function onboardingWizard() { contactEmail: '{{ user.email | default("") | e }}', selectedTier: 'free', billingCycle: 'monthly', + annual: false, selectedDestination: '', get progressPercent() { @@ -459,8 +464,10 @@ function onboardingWizard() { this.loading = true; this.error = ''; try { const r = await fetch('/api/onboarding/complete', { method: 'POST' }); - if (r.ok) { window.location.href = '/upload'; } - else { const d = await r.json(); this.error = d.detail || 'Failed to complete onboarding'; } + if (r.ok) { + const data = await r.json(); + window.location.href = data.redirect_url || '/upload'; + } else { const d = await r.json(); this.error = d.detail || 'Failed to complete onboarding'; } } catch (_) { this.error = 'Network error. Please try again.'; } finally { this.loading = false; } } diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index 6cc27571..7dd29631 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -278,7 +278,9 @@ class TestOnboardingAPI: _make_profile(ob_session, _TEST_USER["sub"]) resp = ob_client_authed.post("/api/onboarding/complete") assert resp.status_code == 200 - assert resp.json() == {"success": True} + data = resp.json() + assert data["success"] is True + assert "redirect_url" in data # Re-query to see persisted value ob_session.expire_all()