Compare commits

..

1 Commits

Author SHA1 Message Date
ي
cadcb8077d Add mode-aware pricing redirect for podcast demo flow 2026-03-30 07:48:00 +05:30
3 changed files with 45 additions and 72 deletions

View File

@@ -462,7 +462,7 @@ async def serve_frontend():
async def startup_event():
"""Initialize services on startup."""
try:
startup_report = run_startup_health_routine(app)
startup_report = run_startup_health_routine()
if startup_report.get("status") != "healthy":
logger.error(f"Startup readiness finished with failures: {startup_report.get('errors', [])}")

View File

@@ -3,8 +3,6 @@ from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional
from fastapi import FastAPI
from fastapi.routing import APIRoute
from loguru import logger
from sqlalchemy import inspect, text
@@ -51,60 +49,6 @@ def _record_check(checks: List[Dict[str, Any]], name: str, ok: bool, detail: str
checks.append({"name": name, "ok": ok, "detail": detail})
def _is_demo_mode() -> bool:
app_env = os.getenv("APP_ENV", os.getenv("ENV", os.getenv("DEPLOY_ENV", ""))).strip().lower()
if app_env == "demo":
return True
return _env_true("ALWRITY_DEMO_MODE", default=False)
def _check_required_demo_routes(
app: Optional[FastAPI],
checks: List[Dict[str, Any]],
errors: List[str],
) -> None:
if not _is_demo_mode():
_record_check(
checks,
"demo_required_routes",
True,
"Skipped (not in demo mode). Set APP_ENV=demo or ALWRITY_DEMO_MODE=true to enforce.",
)
return
if app is None:
errors.append(
"Demo startup route check could not run because FastAPI app context was not provided to startup health routine."
)
_record_check(checks, "demo_required_routes_context", False, "missing app context")
return
required_routes = {
"/api/subscription/plans": "GET",
"/api/podcast/projects": "GET",
}
available_routes = {
(route.path, method)
for route in app.router.routes
if isinstance(route, APIRoute)
for method in route.methods
}
missing: List[str] = []
for path, method in required_routes.items():
if (path, method) in available_routes:
_record_check(checks, f"demo_route_{path}_{method}", True, "route registered")
else:
missing.append(f"{method} {path}")
_record_check(checks, f"demo_route_{path}_{method}", False, "route missing")
if missing:
errors.append(
"Demo mode startup check failed. Missing required API endpoints: "
f"{', '.join(missing)}. Ensure subscription and podcast routers are imported and included during app setup."
)
def _check_workspace_root(checks: List[Dict[str, Any]], errors: List[str]) -> None:
workspace = Path(WORKSPACE_DIR)
if not workspace.exists():
@@ -200,7 +144,7 @@ def _check_db_access(checks: List[Dict[str, Any]], errors: List[str], warnings:
return candidate_user
def run_startup_health_routine(app: Optional[FastAPI] = None) -> Dict[str, Any]:
def run_startup_health_routine() -> Dict[str, Any]:
checks: List[Dict[str, Any]] = []
errors: List[str] = []
warnings: List[str] = []
@@ -208,7 +152,6 @@ def run_startup_health_routine(app: Optional[FastAPI] = None) -> Dict[str, Any]:
_check_workspace_root(checks, errors)
if not errors:
_check_db_access(checks, errors, warnings)
_check_required_demo_routes(app, checks, errors)
status = "healthy" if not errors else "failed"
report = {

View File

@@ -72,6 +72,39 @@ const PricingPage: React.FC = () => {
fetchPlans();
}, []);
const isPodcastOnlyDemoMode = () => {
const appMode = (localStorage.getItem('app_mode') || '').toLowerCase();
const demoMode = (localStorage.getItem('demo_mode') || '').toLowerCase();
const podcastOnlyDemoMode = (localStorage.getItem('podcast_only_demo_mode') || '').toLowerCase();
const envAppMode = (process.env.REACT_APP_APP_MODE || '').toLowerCase();
const envDemoMode = (process.env.REACT_APP_DEMO_MODE || '').toLowerCase();
return (
podcastOnlyDemoMode === 'true' ||
appMode === 'podcast-only' ||
demoMode === 'podcast-only' ||
envAppMode === 'podcast-only' ||
envDemoMode === 'podcast-only'
);
};
const redirectAfterSubscription = () => {
// In podcast-only demo mode, always force users into podcast flow.
// Never send demo users to onboarding.
if (isPodcastOnlyDemoMode()) {
navigate('/podcast-maker');
return;
}
// Full mode keeps existing onboarding redirect behavior.
const onboardingComplete = localStorage.getItem('onboarding_complete') === 'true';
if (onboardingComplete) {
navigate('/dashboard');
} else {
navigate('/onboarding');
}
};
const fetchPlans = async () => {
try {
setLoading(true);
@@ -133,14 +166,7 @@ const PricingPage: React.FC = () => {
// Refresh subscription status
window.dispatchEvent(new CustomEvent('subscription-updated'));
// After subscription, check if onboarding is complete
// If not complete, redirect to onboarding; otherwise to dashboard
const onboardingComplete = localStorage.getItem('onboarding_complete') === 'true';
if (onboardingComplete) {
navigate('/dashboard');
} else {
navigate('/onboarding');
}
redirectAfterSubscription();
} catch (err) {
console.error('Error subscribing:', err);
setError('Failed to process subscription');
@@ -240,10 +266,13 @@ const PricingPage: React.FC = () => {
setTimeout(() => {
clearInterval(countdownInterval);
// After subscription, check if onboarding is complete
// If not complete, redirect to onboarding; otherwise to dashboard
const onboardingComplete = localStorage.getItem('onboarding_complete') === 'true';
if (onboardingComplete) {
// In podcast-only demo mode, always route users to podcast flow.
if (isPodcastOnlyDemoMode()) {
navigate('/podcast-maker');
} else {
const onboardingComplete = localStorage.getItem('onboarding_complete') === 'true';
if (onboardingComplete) {
// Restore navigation state (path, phase, tool) if available
const navState = restoreNavigationState();
@@ -266,7 +295,8 @@ const PricingPage: React.FC = () => {
}
}
} else {
navigate('/onboarding');
navigate('/onboarding');
}
}
}, 3000);
} catch (err) {