Land on account overview and import hosted sites.
Deploy Ladill Hosting / deploy (push) Successful in 25s

Route launcher and home hub entry to the account overview instead of the
control panel, label the sidebar app launcher, and extend hosting:import
to sync hosted_sites with platform_id matching for existing accounts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-06 17:13:46 +00:00
co-authored by Cursor
parent 1e134a8e4b
commit e39a47fa1f
6 changed files with 69 additions and 8 deletions
+46 -1
View File
@@ -3,6 +3,7 @@
namespace App\Console\Commands;
use App\Models\CustomerHostingOrder;
use App\Models\HostedSite;
use App\Models\HostingAccount;
use App\Models\HostingAccountMember;
use App\Models\HostingBillingInvoice;
@@ -72,6 +73,9 @@ class ImportHostingCommand extends Command
foreach ($payload['hosting_accounts'] ?? [] as $row) {
$this->importAccount($row, $commit);
}
foreach ($payload['hosted_sites'] ?? [] as $row) {
$this->importSite($row, $commit);
}
foreach ($payload['customer_hosting_orders'] ?? [] as $row) {
$this->importCustomerOrder($row, $commit);
}
@@ -90,6 +94,10 @@ class ImportHostingCommand extends Command
});
$this->info(($commit ? 'Imported' : 'Would import').' '.count($this->accountMap).' hosting account(s).');
$siteCount = count($payload['hosted_sites'] ?? []);
if ($siteCount > 0) {
$this->info(($commit ? 'Imported' : 'Would import')." {$siteCount} hosted site(s).");
}
return self::SUCCESS;
}
@@ -134,7 +142,18 @@ class ImportHostingCommand extends Command
}
if ($commit) {
$account = HostingAccount::updateOrCreate(['platform_id' => $platformId], $row);
$account = HostingAccount::query()->where('platform_id', $platformId)->first();
if (! $account && ($row['username'] ?? '') !== '') {
$account = HostingAccount::query()->where('username', $row['username'])->first();
}
if ($account) {
$account->update($row);
} else {
$account = HostingAccount::create($row);
}
$this->accountMap[$platformId] = $account->id;
} else {
$this->accountMap[$platformId] = $platformId;
@@ -142,6 +161,32 @@ class ImportHostingCommand extends Command
}
}
/** @param array<string, mixed> $row */
private function importSite(array $row, bool $commit): void
{
$platformAccountId = (int) ($row['platform_hosting_account_id'] ?? 0);
$localAccountId = $this->accountMap[$platformAccountId] ?? null;
if (! $localAccountId) {
$this->warn("Skipping site {$row['domain']}: unknown hosting account #{$platformAccountId}");
return;
}
unset($row['platform_hosting_account_id'], $row['platform_id']);
$row['hosting_account_id'] = $localAccountId;
$row['domain_id'] = null;
if ($commit) {
HostedSite::withTrashed()->updateOrCreate(
['hosting_account_id' => $localAccountId, 'domain' => $row['domain']],
$row
);
} else {
$this->line(" site {$row['domain']} → account {$localAccountId}");
}
}
/** @param array<string, mixed> $row */
private function importCustomerOrder(array $row, bool $commit): void
{
@@ -76,10 +76,11 @@ class HostingProductController extends Controller
]);
}
// Single account - redirect directly to panel
// Single account — account overview (matches monolith; panel via Control Panel)
if ($allAccounts->count() === 1) {
$account = $allAccounts->first();
return redirect()->route('hosting.panel.index', $account);
return redirect()->route('hosting.accounts.show', $account);
}
// Multiple accounts - show list page
+1
View File
@@ -35,6 +35,7 @@ class HostingAccount extends Model
public const GRACE_PERIOD_MONTHS = 3;
protected $fillable = [
'platform_id',
'user_id',
'hosting_product_id',
'hosting_node_id',
@@ -10,7 +10,7 @@
<div class="space-y-4">
@foreach($accounts as $account)
<a href="{{ route('hosting.panel.index', $account) }}"
<a href="{{ route('hosting.accounts.show', $account) }}"
class="group block rounded-xl border border-slate-200 bg-white p-5 hover:border-indigo-300 hover:shadow-md transition-all duration-200">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
+15 -3
View File
@@ -4,6 +4,7 @@
// from public/images/launcher-icons/. Omits the current app (matched by
// APP_URL host). To replicate elsewhere, copy this file + the config +
// the launcher-icons folder verbatim.
$sidebar = (bool) ($sidebar ?? false);
$selfHost = parse_url((string) config('app.url'), PHP_URL_HOST);
$launcherApps = array_values(array_filter(
config('ladill_launcher.apps', []),
@@ -13,17 +14,28 @@
@if (! empty($launcherApps))
<div class="relative" x-data="{ appsOpen: false }" @click.outside="appsOpen = false" @keydown.escape.window="appsOpen = false">
<button type="button" @click="appsOpen = !appsOpen"
class="inline-flex items-center justify-center rounded-xl border border-slate-200 p-2 text-slate-600 transition hover:bg-slate-50"
@class([
'inline-flex items-center justify-center rounded-xl border border-slate-200 text-slate-600 transition hover:bg-slate-50',
'p-2' => ! $sidebar,
'w-full gap-3 px-3 py-2 text-[13px] hover:text-slate-900' => $sidebar,
])
:class="appsOpen ? 'bg-slate-50 text-slate-900' : ''" aria-label="All apps">
<svg class="h-5 w-5" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<svg @class(['shrink-0', 'h-5 w-5' => ! $sidebar, 'h-[18px] w-[18px]' => $sidebar]) viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M4.5 0.5H1.5C0.947715 0.5 0.5 0.947715 0.5 1.5V4.5C0.5 5.05228 0.947715 5.5 1.5 5.5H4.5C5.05228 5.5 5.5 5.05228 5.5 4.5V1.5C5.5 0.947715 5.05228 0.5 4.5 0.5Z"/>
<path d="M12.5 0.5H9.5C8.94772 0.5 8.5 0.947715 8.5 1.5V4.5C8.5 5.05228 8.94772 5.5 9.5 5.5H12.5C13.0523 5.5 13.5 5.05228 13.5 4.5V1.5C13.5 0.947715 13.0523 0.5 12.5 0.5Z"/>
<path d="M4.5 8.5H1.5C0.947715 8.5 0.5 8.94772 0.5 9.5V12.5C0.5 13.0523 0.947715 13.5 1.5 13.5H4.5C5.05228 13.5 5.5 13.0523 5.5 12.5V9.5C5.5 8.94772 5.05228 8.5 4.5 8.5Z"/>
<path d="M12.5 8.5H9.5C8.94772 8.5 8.5 8.94772 8.5 9.5V12.5C8.5 13.0523 8.94772 13.5 9.5 13.5H12.5C13.0523 13.5 13.5 13.0523 13.5 12.5V9.5C13.5 8.94772 13.0523 8.5 12.5 8.5Z"/>
</svg>
@if ($sidebar)
<span class="font-medium">All Ladill apps</span>
@endif
</button>
<div x-show="appsOpen" x-cloak x-transition.origin.top.right
class="absolute right-0 mt-2 w-72 rounded-2xl border border-slate-200 bg-white p-3 shadow-xl z-50">
@class([
'absolute z-50 w-72 rounded-2xl border border-slate-200 bg-white p-3 shadow-xl',
'right-0 mt-2' => ! $sidebar,
'bottom-full left-0 mb-2' => $sidebar,
])>
<p class="px-1 pb-2 text-[10px] font-bold uppercase tracking-widest text-slate-400">All apps</p>
<div class="grid grid-cols-3 gap-1">
@foreach ($launcherApps as $app)
+3 -1
View File
@@ -47,5 +47,7 @@
</a>
@endforeach
</nav>
@include('partials.launcher')
<div class="mt-auto border-t border-slate-100 p-3">
@include('partials.launcher', ['sidebar' => true])
</div>
</div>