feat(hosting): add Build POC button with template selector on site detail

Draft sites with a linked prospect show a "Build POC from Template"
section with:
- Template dropdown (generic, restaurant, construction, auto-parts,
  professional-services) loaded from /admin/hosting/sites/templates API
- "Build POC" button that calls POST /admin/hosting/sites/poc/build
- Loading state + success message with pages created count
- Auto-refreshes site detail after build (status changes to POC_READY)

Visible only for draft sites with a prospect_id.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-02 23:24:19 +02:00
parent 73d453d78a
commit 07cd66a0e3

View File

@@ -39,6 +39,29 @@
</div>
</div>
<!-- Build POC (draft sites only) -->
<div x-show="site.status === 'draft' && site.prospect_id" class="p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">Build POC from Template</h3>
<div class="flex flex-wrap items-end gap-3">
<div class="flex-1 min-w-[200px]">
<select x-model="selectedTemplate"
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-teal-400 focus:outline-none dark:bg-gray-700 dark:text-gray-300">
<option value="">Select a template...</option>
<template x-for="t in templates" :key="t.id">
<option :value="t.id" x-text="t.name + ' — ' + t.description"></option>
</template>
</select>
</div>
<button type="button" @click="buildPoc()" :disabled="!selectedTemplate || buildingPoc"
class="inline-flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-teal-600 border border-transparent rounded-lg hover:bg-teal-700 focus:outline-none disabled:opacity-50">
<span x-show="!buildingPoc" x-html="$icon('sparkles', 'w-4 h-4 mr-2')"></span>
<span x-show="buildingPoc" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
<span x-text="buildingPoc ? 'Building...' : 'Build POC'"></span>
</button>
</div>
<p x-show="pocResult" class="mt-2 text-sm text-green-600" x-text="pocResult"></p>
</div>
<!-- Lifecycle Actions -->
<div class="flex flex-wrap gap-3 p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<button type="button" x-show="site.status === 'draft'" @click="doAction('mark-poc-ready')"
@@ -307,7 +330,39 @@ function hostingSiteDetail(siteId) {
acceptMerchantId: '',
goLiveDomain: '',
newService: { service_type: 'domain', name: '', price_cents: null, billing_period: 'monthly' },
async init() { await this.loadSite(); },
// POC builder
templates: [],
selectedTemplate: '',
buildingPoc: false,
pocResult: '',
async init() {
await this.loadSite();
await this.loadTemplates();
},
async loadTemplates() {
try {
var resp = await apiClient.get('/admin/hosting/sites/templates');
this.templates = resp.templates || [];
} catch (e) { /* ignore */ }
},
async buildPoc() {
if (!this.selectedTemplate || !this.site.prospect_id) return;
this.buildingPoc = true;
this.pocResult = '';
try {
var result = await apiClient.post('/admin/hosting/sites/poc/build', {
prospect_id: this.site.prospect_id,
template_id: this.selectedTemplate,
});
this.pocResult = 'POC built! ' + result.pages_created + ' pages created.';
Utils.showToast('POC built successfully', 'success');
await this.loadSite();
} catch (e) {
Utils.showToast('Build failed: ' + e.message, 'error');
} finally {
this.buildingPoc = false;
}
},
async loadSite() {
this.loading = true;
this.error = null;