feat: production routing support for subdomain and custom domain modes
Some checks failed
Some checks failed
Double-mount store routes at /store/* and /store/{store_code}/* so the
same handlers work in dev path-based, prod path-based, prod subdomain,
and prod custom-domain modes. Wire StorePlatform.custom_subdomain into
StoreContextMiddleware for per-platform subdomain overrides. Add admin
custom-domain management UI, fix stale /shop/ reset link, add
/merchants/ to reserved paths, and server-render window.STORE_CODE for
JS that previously parsed the URL.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -356,6 +356,142 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom Domains -->
|
||||
<div class="px-4 py-3 mb-8 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">
|
||||
Custom Domains
|
||||
</h3>
|
||||
<button
|
||||
@click="showAddDomainForm = !showAddDomainForm"
|
||||
class="flex items-center px-3 py-1.5 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700">
|
||||
<span x-html="$icon('plus', 'w-4 h-4 mr-1')"></span>
|
||||
Add Domain
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Add Domain Form -->
|
||||
<div x-show="showAddDomainForm" x-transition class="mb-4 p-4 bg-gray-50 rounded-lg dark:bg-gray-700">
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-1">Domain</label>
|
||||
<input
|
||||
type="text"
|
||||
x-model="newDomain.domain"
|
||||
placeholder="shop.example.com"
|
||||
class="w-full px-3 py-2 text-sm border rounded-lg dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-500" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-1">Platform</label>
|
||||
<select
|
||||
x-model="newDomain.platform_id"
|
||||
class="w-full px-3 py-2 text-sm border rounded-lg dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-500">
|
||||
<option value="">Select platform...</option>
|
||||
<template x-for="entry in subscriptions" :key="entry.subscription?.id">
|
||||
<option :value="entry.platform_id" x-text="entry.platform_name"></option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-3">
|
||||
<button
|
||||
@click="showAddDomainForm = false; newDomain = {domain: '', platform_id: ''}"
|
||||
class="px-3 py-1.5 text-sm font-medium text-gray-600 bg-gray-200 rounded-lg hover:bg-gray-300 dark:bg-gray-600 dark:text-gray-300 dark:hover:bg-gray-500">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
@click="addDomain()"
|
||||
:disabled="!newDomain.domain || domainSaving"
|
||||
class="px-3 py-1.5 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
<span x-show="!domainSaving">Add Domain</span>
|
||||
<span x-show="domainSaving">Adding...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Domains Loading -->
|
||||
<div x-show="domainsLoading" class="text-center py-4">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Loading domains...</p>
|
||||
</div>
|
||||
|
||||
<!-- Domains List -->
|
||||
<div x-show="!domainsLoading && domains.length > 0" class="space-y-3">
|
||||
<template x-for="domain in domains" :key="domain.id">
|
||||
<div class="p-3 bg-gray-50 rounded-lg dark:bg-gray-700 flex items-center justify-between">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200 truncate" x-text="domain.domain"></span>
|
||||
<span x-show="domain.is_primary"
|
||||
class="px-1.5 py-0.5 text-xs font-medium text-purple-800 bg-purple-100 rounded-full dark:bg-purple-900 dark:text-purple-300">
|
||||
Primary
|
||||
</span>
|
||||
<span :class="domain.is_verified
|
||||
? 'text-green-800 bg-green-100 dark:bg-green-900 dark:text-green-300'
|
||||
: 'text-yellow-800 bg-yellow-100 dark:bg-yellow-900 dark:text-yellow-300'"
|
||||
class="px-1.5 py-0.5 text-xs font-medium rounded-full"
|
||||
x-text="domain.is_verified ? 'Verified' : 'Unverified'">
|
||||
</span>
|
||||
<span :class="domain.is_active
|
||||
? 'text-green-800 bg-green-100 dark:bg-green-900 dark:text-green-300'
|
||||
: 'text-red-800 bg-red-100 dark:bg-red-900 dark:text-red-300'"
|
||||
class="px-1.5 py-0.5 text-xs font-medium rounded-full"
|
||||
x-text="domain.is_active ? 'Active' : 'Inactive'">
|
||||
</span>
|
||||
<span x-show="domain.ssl_status === 'active'"
|
||||
class="px-1.5 py-0.5 text-xs font-medium text-green-800 bg-green-100 rounded-full dark:bg-green-900 dark:text-green-300">
|
||||
SSL
|
||||
</span>
|
||||
</div>
|
||||
<!-- Verification instructions (shown when unverified) -->
|
||||
<div x-show="!domain.is_verified && domain.verification_token" class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
Add TXT record: <code class="px-1 py-0.5 bg-gray-200 dark:bg-gray-600 rounded text-xs">_orion-verify.<span x-text="domain.domain"></span></code>
|
||||
with value <code class="px-1 py-0.5 bg-gray-200 dark:bg-gray-600 rounded text-xs" x-text="domain.verification_token"></code>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 ml-3 flex-shrink-0">
|
||||
<!-- Verify Button -->
|
||||
<button
|
||||
x-show="!domain.is_verified"
|
||||
@click="verifyDomain(domain.id)"
|
||||
class="px-2 py-1 text-xs font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400"
|
||||
title="Verify DNS">
|
||||
<span x-html="$icon('badge-check', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
<!-- Toggle Active -->
|
||||
<button
|
||||
x-show="domain.is_verified"
|
||||
@click="toggleDomainActive(domain.id, !domain.is_active)"
|
||||
:class="domain.is_active ? 'text-yellow-600 hover:text-yellow-700' : 'text-green-600 hover:text-green-700'"
|
||||
class="px-2 py-1 text-xs font-medium"
|
||||
:title="domain.is_active ? 'Deactivate' : 'Activate'">
|
||||
<span x-html="$icon(domain.is_active ? 'eye-off' : 'eye', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
<!-- Set Primary -->
|
||||
<button
|
||||
x-show="domain.is_verified && !domain.is_primary"
|
||||
@click="setDomainPrimary(domain.id)"
|
||||
class="px-2 py-1 text-xs font-medium text-purple-600 hover:text-purple-700 dark:text-purple-400"
|
||||
title="Set as primary">
|
||||
<span x-html="$icon('star', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
<!-- Delete -->
|
||||
<button
|
||||
@click="deleteDomain(domain.id, domain.domain)"
|
||||
class="px-2 py-1 text-xs font-medium text-red-600 hover:text-red-700 dark:text-red-400"
|
||||
title="Delete domain">
|
||||
<span x-html="$icon('delete', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- No Domains -->
|
||||
<div x-show="!domainsLoading && domains.length === 0" class="text-center py-4">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">No custom domains configured.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- More Actions -->
|
||||
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
||||
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
||||
|
||||
Reference in New Issue
Block a user