feat: module-driven onboarding system + simplified 3-step signup

Add OnboardingProviderProtocol so modules declare their own post-signup
onboarding steps. The core OnboardingAggregator discovers enabled
providers and exposes a dashboard API (GET /dashboard/onboarding).
A session-scoped banner on the store dashboard shows a checklist that
guides merchants through setup without blocking signup.

Signup is simplified from 4 steps to 3 (Plan → Account → Payment):
store creation is merged into account creation, store language is
captured from the user's browsing language, and platform-specific
template branching is removed.

Includes 47 unit and integration tests covering all new providers,
the aggregator, the API endpoint, and the signup service changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 23:39:42 +01:00
parent f8a2394da5
commit ef9ea29643
26 changed files with 2055 additions and 699 deletions

View File

@@ -0,0 +1,79 @@
# app/modules/marketplace/services/marketplace_onboarding.py
"""
Onboarding provider for the marketplace module.
Provides two onboarding steps:
1. Connect Letzshop API - completed when StoreLetzshopCredentials exists with api_key
2. Import products - completed when at least 1 product exists in catalog
"""
import logging
from sqlalchemy.orm import Session
from app.modules.contracts.onboarding import OnboardingStepDefinition
logger = logging.getLogger(__name__)
class MarketplaceOnboardingProvider:
"""Onboarding provider for marketplace module."""
@property
def onboarding_category(self) -> str:
return "marketplace"
def get_onboarding_steps(self) -> list[OnboardingStepDefinition]:
return [
OnboardingStepDefinition(
key="marketplace.connect_api",
title_key="onboarding.marketplace.connect_api.title",
description_key="onboarding.marketplace.connect_api.description",
icon="plug",
route_template="/store/{store_code}/letzshop",
order=200,
category="marketplace",
),
OnboardingStepDefinition(
key="marketplace.import_products",
title_key="onboarding.marketplace.import_products.title",
description_key="onboarding.marketplace.import_products.description",
icon="package",
route_template="/store/{store_code}/marketplace",
order=210,
category="marketplace",
),
]
def is_step_completed(
self, db: Session, store_id: int, step_key: str
) -> bool:
if step_key == "marketplace.connect_api":
return self._has_letzshop_credentials(db, store_id)
if step_key == "marketplace.import_products":
return self._has_products(db, store_id)
return False
def _has_letzshop_credentials(self, db: Session, store_id: int) -> bool:
from app.modules.marketplace.models.letzshop import StoreLetzshopCredentials
creds = (
db.query(StoreLetzshopCredentials)
.filter(StoreLetzshopCredentials.store_id == store_id)
.first()
)
return bool(creds and creds.api_key_encrypted)
def _has_products(self, db: Session, store_id: int) -> bool:
from app.modules.catalog.models.product import Product
count = (
db.query(Product)
.filter(Product.store_id == store_id)
.limit(1)
.count()
)
return count > 0
marketplace_onboarding_provider = MarketplaceOnboardingProvider()