fix(hosting): site detail null guard + cleaner preview URLs

- Site detail template: x-show → x-if to prevent Alpine evaluating
  expressions when site is null during async loading
- Slugify: prefer domain_name over business_name for subdomain
  generation (batirenovation-strasbourg vs bati-rnovation-strasbourg-
  peinture-ravalement-dans). Cap at 30 chars. Strip protocol/TLD.
- POC builder passes domain_name for clean slugs
- Remove .lu/.fr/.com TLD from slugs automatically

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-02 22:49:32 +02:00
parent cff0af31be
commit d380437594
3 changed files with 17 additions and 4 deletions

View File

@@ -35,11 +35,19 @@ ALLOWED_TRANSITIONS: dict[HostedSiteStatus, list[HostedSiteStatus]] = {
def _slugify(name: str) -> str: def _slugify(name: str) -> str:
"""Generate a URL-safe slug from a business name.""" """Generate a short URL-safe slug from a business name or domain."""
slug = name.lower().strip() slug = name.lower().strip()
# Remove protocol/www if domain was passed
for prefix in ["https://", "http://", "www."]:
if slug.startswith(prefix):
slug = slug[len(prefix):]
slug = slug.rstrip("/")
# Remove TLD if it looks like a domain
if "." in slug and " " not in slug:
slug = slug.rsplit(".", 1)[0]
slug = re.sub(r"[^a-z0-9\s-]", "", slug) slug = re.sub(r"[^a-z0-9\s-]", "", slug)
slug = re.sub(r"[\s-]+", "-", slug) slug = re.sub(r"[\s-]+", "-", slug)
return slug.strip("-")[:50] return slug.strip("-")[:30]
class HostedSiteService: class HostedSiteService:
@@ -101,7 +109,9 @@ class HostedSiteService:
business_name = data["business_name"] business_name = data["business_name"]
merchant_id = data.get("merchant_id") merchant_id = data.get("merchant_id")
prospect_id = data.get("prospect_id") prospect_id = data.get("prospect_id")
slug = _slugify(business_name) # Prefer domain_name for slug (shorter, cleaner), fall back to business_name
slug_source = data.get("domain_name") or business_name
slug = _slugify(slug_source)
# Find hosting platform # Find hosting platform
platform = db.query(Platform).filter(Platform.code == "hosting").first() platform = db.query(Platform).filter(Platform.code == "hosting").first()

View File

@@ -60,6 +60,7 @@ class PocBuilderService:
# 4. Create HostedSite + Store # 4. Create HostedSite + Store
site_data = { site_data = {
"business_name": context["business_name"], "business_name": context["business_name"],
"domain_name": prospect.domain_name, # used for clean subdomain slug
"prospect_id": prospect_id, "prospect_id": prospect_id,
"contact_email": context.get("email"), "contact_email": context.get("email"),
"contact_phone": context.get("phone"), "contact_phone": context.get("phone"),

View File

@@ -12,7 +12,8 @@
{{ loading_state('Loading site...') }} {{ loading_state('Loading site...') }}
{{ error_state('Error loading site') }} {{ error_state('Error loading site') }}
<div x-show="!loading && !error && site" class="space-y-6"> <template x-if="!loading && !error && site">
<div class="space-y-6">
<!-- Header --> <!-- Header -->
<div class="flex flex-col sm:flex-row sm:items-center justify-between my-6 gap-4"> <div class="flex flex-col sm:flex-row sm:items-center justify-between my-6 gap-4">
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
@@ -187,6 +188,7 @@
</div> </div>
</div> </div>
</div> </div>
</template>
<!-- Send Proposal Modal --> <!-- Send Proposal Modal -->
{% call modal('proposalModal', 'Send Proposal', show_var='showProposalModal', size='md', show_footer=false) %} {% call modal('proposalModal', 'Send Proposal', show_var='showProposalModal', size='md', show_footer=false) %}