Compare commits

..

1 Commits

Author SHA1 Message Date
ي
ec9d2f922e Add podcast-only demo mode guards in app router setup 2026-03-30 07:07:24 +05:30
3 changed files with 218 additions and 258 deletions

View File

@@ -48,6 +48,8 @@ load_dotenv(backend_dir / '.env') # backend/.env
load_dotenv(project_root / '.env') # root .env (fallback) load_dotenv(project_root / '.env') # root .env (fallback)
load_dotenv() # CWD .env (fallback) load_dotenv() # CWD .env (fallback)
PODCAST_ONLY_DEMO_MODE = os.getenv("PODCAST_ONLY_DEMO_MODE", "false").lower() in {"1", "true", "yes", "on"}
# Set up clean logging for end users # Set up clean logging for end users
from logging_config import setup_clean_logging from logging_config import setup_clean_logging
setup_clean_logging() setup_clean_logging()
@@ -110,7 +112,8 @@ from services.startup_health import (
# Import OAuth token monitoring routes # Import OAuth token monitoring routes
from api.oauth_token_monitoring_routes import router as oauth_token_monitoring_router from api.oauth_token_monitoring_routes import router as oauth_token_monitoring_router
# Import SEO Dashboard endpoints if not PODCAST_ONLY_DEMO_MODE:
# Import SEO Dashboard endpoints only when non-demo features are enabled
from api.seo_dashboard import ( from api.seo_dashboard import (
get_seo_dashboard_data, get_seo_dashboard_data,
get_seo_health_score, get_seo_health_score,
@@ -259,12 +262,17 @@ async def onboarding_status():
return onboarding_manager.get_onboarding_status() return onboarding_manager.get_onboarding_status()
# Include routers using modular utilities # Include routers using modular utilities
if not PODCAST_ONLY_DEMO_MODE:
router_manager.include_core_routers() router_manager.include_core_routers()
router_manager.include_optional_routers() router_manager.include_optional_routers()
else:
logger.info("PODCAST_ONLY_DEMO_MODE enabled: including only podcast and subscription feature routers.")
app.include_router(subscription_router)
# Include assets serving router (must be mounted to serve generated images) # Include assets serving router (must be mounted to serve generated images)
app.include_router(assets_serving_router) app.include_router(assets_serving_router)
if not PODCAST_ONLY_DEMO_MODE:
# SEO Dashboard endpoints # SEO Dashboard endpoints
@app.get("/api/seo-dashboard/data") @app.get("/api/seo-dashboard/data")
async def seo_dashboard_data(): async def seo_dashboard_data():
@@ -332,8 +340,6 @@ async def refresh_analytics_data_endpoint(current_user: dict = Depends(get_curre
"""Refresh analytics data by invalidating cache and fetching fresh data.""" """Refresh analytics data by invalidating cache and fetching fresh data."""
return await refresh_analytics_data(current_user, site_url) return await refresh_analytics_data(current_user, site_url)
@app.get("/api/seo-dashboard/onboarding-task-health") @app.get("/api/seo-dashboard/onboarding-task-health")
async def onboarding_task_health_endpoint(current_user: dict = Depends(get_current_user), site_url: str = None): async def onboarding_task_health_endpoint(current_user: dict = Depends(get_current_user), site_url: str = None):
"""Get consolidated health for onboarding-scheduled SEO tasks.""" """Get consolidated health for onboarding-scheduled SEO tasks."""
@@ -347,31 +353,17 @@ async def seo_dashboard_health():
# Phase 2B: Semantic health monitoring endpoint (24-hour polling) # Phase 2B: Semantic health monitoring endpoint (24-hour polling)
@app.get("/api/seo-dashboard/semantic-health") @app.get("/api/seo-dashboard/semantic-health")
async def semantic_health_endpoint(current_user: dict = Depends(get_current_user)): async def semantic_health_endpoint(current_user: dict = Depends(get_current_user)):
""" """Get real-time semantic health metrics for content and competitors."""
Get real-time semantic health metrics for content and competitors.
This endpoint provides Phase 2B semantic intelligence monitoring data.
Returns semantic health score, status, and recommendations.
Data is cached and updated every 24 hours via scheduler.
"""
return await get_semantic_health(current_user) return await get_semantic_health(current_user)
@app.get("/api/seo-dashboard/cache-stats") @app.get("/api/seo-dashboard/cache-stats")
async def semantic_cache_stats_endpoint(current_user: dict = Depends(get_current_user)): async def semantic_cache_stats_endpoint(current_user: dict = Depends(get_current_user)):
""" """Get semantic cache performance statistics."""
Get semantic cache performance statistics.
Returns hit rate, memory usage, and eviction counts.
"""
return await get_semantic_cache_stats(current_user) return await get_semantic_cache_stats(current_user)
@app.get("/api/seo-dashboard/sif-health") @app.get("/api/seo-dashboard/sif-health")
async def sif_indexing_health_endpoint(current_user: dict = Depends(get_current_user)): async def sif_indexing_health_endpoint(current_user: dict = Depends(get_current_user)):
""" """Get SIF indexing health summary for the current user."""
Get SIF indexing health summary for the current user.
Used by the Semantic Indexing Status widget on the dashboard.
"""
return await get_sif_indexing_health(current_user) return await get_sif_indexing_health(current_user)
# Comprehensive SEO Analysis endpoints # Comprehensive SEO Analysis endpoints
@@ -424,6 +416,7 @@ app.include_router(content_assets_router)
from api.podcast.router import router as podcast_router from api.podcast.router import router as podcast_router
app.include_router(podcast_router) app.include_router(podcast_router)
if not PODCAST_ONLY_DEMO_MODE:
# Include YouTube Creator Studio router # Include YouTube Creator Studio router
from api.youtube.router import router as youtube_router from api.youtube.router import router as youtube_router
app.include_router(youtube_router, prefix="/api") app.include_router(youtube_router, prefix="/api")

View File

@@ -52,27 +52,6 @@ export interface SubscriptionPlan {
} }
const PricingPage: React.FC = () => { const PricingPage: React.FC = () => {
const pricingMode = (process.env.REACT_APP_PRICING_MODE || 'alpha').toLowerCase();
const isAlphaMode = pricingMode === 'alpha';
const tierPolicyByMode: Record<string, { visible: string[]; selectable: string[]; unavailableLabels: Record<string, string> }> = {
alpha: {
visible: ['free', 'basic', 'pro', 'enterprise'],
selectable: ['free', 'basic'],
unavailableLabels: { pro: 'Coming Soon', enterprise: 'Contact Sales' },
},
demo: {
visible: ['free', 'basic', 'pro', 'enterprise'],
selectable: ['free', 'basic', 'pro'],
unavailableLabels: { enterprise: 'Contact Sales' },
},
production: {
visible: ['free', 'basic', 'pro', 'enterprise'],
selectable: ['free', 'basic', 'pro'],
unavailableLabels: { enterprise: 'Contact Sales' },
},
};
const activeTierPolicy = tierPolicyByMode[pricingMode] || tierPolicyByMode.alpha;
const navigate = useNavigate(); const navigate = useNavigate();
const [plans, setPlans] = useState<SubscriptionPlan[]>([]); const [plans, setPlans] = useState<SubscriptionPlan[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -97,11 +76,9 @@ const PricingPage: React.FC = () => {
try { try {
setLoading(true); setLoading(true);
const response = await apiClient.get('/api/subscription/plans'); const response = await apiClient.get('/api/subscription/plans');
// Filter out legacy alpha-named plans and enforce tier visibility policy. // Filter out any alpha plans and ensure we only show the 4 main tiers
const filteredPlans = response.data.data.plans.filter( const filteredPlans = response.data.data.plans.filter(
(plan: SubscriptionPlan) => (plan: SubscriptionPlan) => !plan.name.toLowerCase().includes('alpha')
!plan.name.toLowerCase().includes('alpha') &&
activeTierPolicy.visible.includes(plan.tier)
); );
setPlans(filteredPlans); setPlans(filteredPlans);
} catch (err) { } catch (err) {
@@ -134,13 +111,10 @@ const PricingPage: React.FC = () => {
return; return;
} }
if (!activeTierPolicy.selectable.includes(plan.tier)) { // For alpha testing, only allow Free and Basic plans (Pro features not ready)
if (plan.tier !== 'free' && plan.tier !== 'basic') {
console.error('[PricingPage] Plan tier not available:', plan.tier); console.error('[PricingPage] Plan tier not available:', plan.tier);
setError( setError('This plan is not available for alpha testing');
isAlphaMode
? 'This plan is not available during alpha testing'
: 'This plan is currently not available for self-serve checkout'
);
return; return;
} }
@@ -377,8 +351,6 @@ const PricingPage: React.FC = () => {
yearlyBilling={yearlyBilling} yearlyBilling={yearlyBilling}
selectedPlanId={selectedPlan} selectedPlanId={selectedPlan}
subscribing={subscribing} subscribing={subscribing}
canSelectPlan={activeTierPolicy.selectable.includes(plan.tier)}
unavailableLabel={activeTierPolicy.unavailableLabels[plan.tier]}
onSelectPlan={setSelectedPlan} onSelectPlan={setSelectedPlan}
onSubscribe={handleSubscribe} onSubscribe={handleSubscribe}
openKnowMoreModal={openKnowMoreModal} openKnowMoreModal={openKnowMoreModal}
@@ -420,11 +392,10 @@ const PricingPage: React.FC = () => {
}}> }}>
<Typography variant="h6" component="h2" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Typography variant="h6" component="h2" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Warning sx={{ color: 'warning.main' }} /> <Warning sx={{ color: 'warning.main' }} />
{isAlphaMode ? 'Alpha Testing Subscription' : 'Confirm Subscription'} Alpha Testing Subscription
</Typography> </Typography>
{isAlphaMode ? ( {/* Alpha Testing Notice */}
<>
<Alert severity="warning" sx={{ mb: 2 }}> <Alert severity="warning" sx={{ mb: 2 }}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 0.5 }}> <Typography variant="body2" sx={{ fontWeight: 600, mb: 0.5 }}>
⚠️ Alpha Testing Mode - No Payment Required ⚠️ Alpha Testing Mode - No Payment Required
@@ -435,9 +406,10 @@ const PricingPage: React.FC = () => {
</Alert> </Alert>
<Typography variant="body1" sx={{ mb: 2 }}> <Typography variant="body1" sx={{ mb: 2 }}>
Thank you for participating in our alpha testing! We&apos;re crediting this plan to your account. Thank you for participating in our alpha testing! We're crediting the Basic plan ($29 value) to your account.
</Typography> </Typography>
{/* TODO: Payment Integration Notice */}
<Box sx={{ <Box sx={{
p: 2, p: 2,
mb: 3, mb: 3,
@@ -456,12 +428,6 @@ const PricingPage: React.FC = () => {
Upgrade/downgrade options Upgrade/downgrade options
</Typography> </Typography>
</Box> </Box>
</>
) : (
<Typography variant="body1" sx={{ mb: 3 }}>
Please confirm to continue with your selected subscription plan.
</Typography>
)}
{/* Note: Current behavior allows renewal without payment verification */} {/* Note: Current behavior allows renewal without payment verification */}
{/* This is intentional for alpha testing but will be secured in production */} {/* This is intentional for alpha testing but will be secured in production */}

View File

@@ -69,8 +69,6 @@ interface PlanCardProps {
yearlyBilling: boolean; yearlyBilling: boolean;
selectedPlanId: number | null; selectedPlanId: number | null;
subscribing: boolean; subscribing: boolean;
canSelectPlan: boolean;
unavailableLabel?: string;
onSelectPlan: (planId: number) => void; onSelectPlan: (planId: number) => void;
onSubscribe: (planId: number) => void; onSubscribe: (planId: number) => void;
openKnowMoreModal: (title: string, content: React.ReactNode) => void; openKnowMoreModal: (title: string, content: React.ReactNode) => void;
@@ -81,8 +79,6 @@ const PlanCard: React.FC<PlanCardProps> = ({
yearlyBilling, yearlyBilling,
selectedPlanId, selectedPlanId,
subscribing, subscribing,
canSelectPlan,
unavailableLabel,
onSelectPlan, onSelectPlan,
onSubscribe, onSubscribe,
openKnowMoreModal, openKnowMoreModal,
@@ -913,9 +909,13 @@ const PlanCard: React.FC<PlanCardProps> = ({
</CardContent> </CardContent>
<CardActions sx={{ justifyContent: 'center', pb: 3, flexDirection: 'column', gap: 1 }}> <CardActions sx={{ justifyContent: 'center', pb: 3, flexDirection: 'column', gap: 1 }}>
{!canSelectPlan ? ( {plan.tier === 'pro' ? (
<Button variant="outlined" size="large" fullWidth disabled sx={{ mb: 1 }}> <Button variant="outlined" size="large" fullWidth disabled sx={{ mb: 1 }}>
{unavailableLabel || 'Unavailable'} Coming Soon
</Button>
) : plan.tier === 'enterprise' ? (
<Button variant="outlined" size="large" fullWidth disabled sx={{ mb: 1 }}>
Contact Sales
</Button> </Button>
) : ( ) : (
<> <>
@@ -951,3 +951,4 @@ const PlanCard: React.FC<PlanCardProps> = ({
}; };
export default PlanCard; export default PlanCard;