Add page heroes to VPS and dedicated server pages.
Deploy Ladill Servers / deploy (push) Successful in 1m49s
Deploy Ladill Servers / deploy (push) Successful in 1m49s
Surface server counts, status breakdown, and available plans in heroes consistent with other Ladill apps. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -172,6 +172,14 @@ class HostingProductController extends Controller
|
|||||||
CustomerHostingOrder::CYCLE_YEARLY => 'Yearly (12 months)',
|
CustomerHostingOrder::CYCLE_YEARLY => 'Yearly (12 months)',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$pendingStatuses = ['pending_payment', 'pending_approval', 'approved', 'provisioning'];
|
||||||
|
$heroStats = [
|
||||||
|
'total' => $orders->count() + $rcServiceOrders->count(),
|
||||||
|
'active' => $orders->where('status', 'active')->count() + $rcServiceOrders->where('status', 'active')->count(),
|
||||||
|
'pending' => $orders->whereIn('status', $pendingStatuses)->count() + $rcServiceOrders->whereIn('status', $pendingStatuses)->count(),
|
||||||
|
'plans' => $products->count(),
|
||||||
|
];
|
||||||
|
|
||||||
return view('hosting.server-type', [
|
return view('hosting.server-type', [
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
'type' => $type,
|
'type' => $type,
|
||||||
@@ -182,6 +190,7 @@ class HostingProductController extends Controller
|
|||||||
'serverOrderPayloads' => $serverOrderPayloads,
|
'serverOrderPayloads' => $serverOrderPayloads,
|
||||||
'billingCycles' => $billingCycles,
|
'billingCycles' => $billingCycles,
|
||||||
'userDomains' => $userDomains,
|
'userDomains' => $userDomains,
|
||||||
|
'heroStats' => $heroStats,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mobile-stats-card {
|
||||||
|
width: calc(66.666667% - 0.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.mobile-stats-card {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Ladill app launcher — cap at 5 rows (15 apps), always-visible scroll indicator */
|
/* Ladill app launcher — cap at 5 rows (15 apps), always-visible scroll indicator */
|
||||||
.ladill-launcher-apps-wrap {
|
.ladill-launcher-apps-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
@props([
|
||||||
|
'badge',
|
||||||
|
'title',
|
||||||
|
'description' => null,
|
||||||
|
'stats' => [],
|
||||||
|
])
|
||||||
|
|
||||||
|
@php
|
||||||
|
$statCols = match (count($stats)) {
|
||||||
|
4 => 'sm:grid-cols-2 lg:grid-cols-4',
|
||||||
|
2 => 'sm:grid-cols-2',
|
||||||
|
default => 'sm:grid-cols-3',
|
||||||
|
};
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div {{ $attributes->merge(['class' => 'relative overflow-hidden rounded-2xl border border-slate-200 bg-white']) }}>
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-br from-indigo-50/80 via-white to-violet-50/60"></div>
|
||||||
|
<div class="relative px-6 py-8 sm:px-10">
|
||||||
|
<div class="flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
|
||||||
|
<div class="max-w-xl">
|
||||||
|
<div class="inline-flex items-center gap-1.5 rounded-full bg-indigo-100 px-3 py-1 text-xs font-semibold text-indigo-700">
|
||||||
|
{{ $badge }}
|
||||||
|
</div>
|
||||||
|
<h1 class="mt-3 text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl">{{ $title }}</h1>
|
||||||
|
@if ($description)
|
||||||
|
<p class="mt-2 text-sm leading-6 text-slate-600">{{ $description }}</p>
|
||||||
|
@endif
|
||||||
|
@isset($actions)
|
||||||
|
<div class="mt-6 flex flex-wrap items-center gap-2">
|
||||||
|
{{ $actions }}
|
||||||
|
</div>
|
||||||
|
@endisset
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (count($stats) > 0)
|
||||||
|
<div class="scrollbar-hide flex snap-x snap-mandatory gap-3 overflow-x-auto pb-2 sm:grid {{ $statCols }} sm:overflow-visible sm:pb-0 lg:gap-4" style="-ms-overflow-style:none;scrollbar-width:none;">
|
||||||
|
@foreach ($stats as $stat)
|
||||||
|
<div class="mobile-stats-card shrink-0 snap-start rounded-xl border border-slate-200 bg-white/90 px-4 py-3 text-center backdrop-blur-sm">
|
||||||
|
<p class="whitespace-nowrap text-xl font-bold text-slate-900 sm:text-2xl">{{ $stat['value'] }}</p>
|
||||||
|
<p class="mt-0.5 whitespace-nowrap text-[11px] font-medium text-slate-500">{{ $stat['label'] }}</p>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -85,20 +85,24 @@
|
|||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<div class="space-y-6" x-data="{ showOrderModal: @js($shouldOpenOrderModal) }">
|
<div class="space-y-6" x-data="{ showOrderModal: @js($shouldOpenOrderModal) }">
|
||||||
{{-- Page Header --}}
|
<x-servers.page-hero
|
||||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
:badge="$type === 'vps' ? 'SSD · Root access · Flexible billing' : 'Bare metal · RAID · Full root'"
|
||||||
<div>
|
:title="$type === 'vps' ? 'VPS' : 'Dedicated servers'"
|
||||||
<h1 class="text-2xl font-semibold tracking-tight text-gray-900">{{ $title }}</h1>
|
:description="$type === 'vps'
|
||||||
<p class="mt-1 text-sm text-gray-500">Manage your {{ strtolower($title) }} orders.</p>
|
? 'Deploy scalable virtual private servers — pick a plan, region, and image.'
|
||||||
</div>
|
: 'Maximum performance and isolation for production workloads on dedicated hardware.'"
|
||||||
|
:stats="[
|
||||||
|
['value' => number_format($heroStats['total']), 'label' => 'Servers'],
|
||||||
|
['value' => number_format($heroStats['active']), 'label' => 'Active'],
|
||||||
|
['value' => number_format($heroStats['pending']), 'label' => 'Pending'],
|
||||||
|
['value' => number_format($heroStats['plans']), 'label' => 'Plans'],
|
||||||
|
]">
|
||||||
@if ($serverOrderPayloads !== [])
|
@if ($serverOrderPayloads !== [])
|
||||||
<button type="button" @click="showOrderModal = true"
|
<x-slot name="actions">
|
||||||
class="inline-flex items-center gap-1.5 rounded-lg bg-gray-900 px-3.5 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-gray-800">
|
<x-btn.create type="button" @click="showOrderModal = true">{{ $orderButtonLabel }}</x-btn.create>
|
||||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg>
|
</x-slot>
|
||||||
{{ $orderButtonLabel }}
|
|
||||||
</button>
|
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</x-servers.page-hero>
|
||||||
|
|
||||||
@if ($hasAnyProducts)
|
@if ($hasAnyProducts)
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user