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:
@@ -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()
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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) %}
|
||||||
|
|||||||
Reference in New Issue
Block a user