Files
ladill-hosting/resources/views/hosting/account.blade.php
T
isaacclad 07bb8beb10
Deploy Ladill Hosting / deploy (push) Successful in 49s
Present Paystack checkout in a responsive mobile sheet and desktop modal.
Standardize customer-facing Ladill Pay checkout shells across products, open them on all viewports, and escape iframe callbacks back to the product flow.
2026-07-21 16:50:07 +00:00

631 lines
48 KiB
PHP

<x-user-layout>
<x-slot name="title">Hosting Account Details</x-slot>
@php
$statusColors = [
'active' => 'bg-emerald-50 text-emerald-700',
'suspended' => 'bg-red-50 text-red-700',
'pending' => 'bg-amber-50 text-amber-700',
'provisioning' => 'bg-blue-50 text-blue-700',
'failed' => 'bg-red-50 text-red-700',
];
$statusLabels = [
'active' => 'Active',
'suspended' => 'Suspended',
'pending' => 'Pending Setup',
'provisioning' => 'Setting Up...',
'failed' => 'Setup Failed',
];
$formErrors = $errors ?? new \Illuminate\Support\ViewErrorBag;
$addDomainAction = \Illuminate\Support\Facades\Route::has('hosting.panel.domains.add')
? route('hosting.panel.domains.add', $account)
: '#';
$panelIndexUrl = \Illuminate\Support\Facades\Route::has('hosting.panel.index')
? route('hosting.panel.index', $account)
: '#';
$panelSettingsUrl = \Illuminate\Support\Facades\Route::has('hosting.panel.settings')
? route('hosting.panel.settings', $account)
: '#';
$resourceStatusColors = [
'active' => 'bg-emerald-50 text-emerald-700',
'throttled' => 'bg-amber-50 text-amber-700',
'suspended' => 'bg-red-50 text-red-700',
];
@endphp
@php
$ownedDomainHosts = collect($ownedDomains)->map(fn ($d) => (string) $d)->all();
$oldDomain = trim((string) old('domain', ''));
$initialDomainSource = empty($ownedDomainHosts)
? 'external'
: ((string) old('is_owned_domain', '1') === '0' ? 'external' : 'ladill');
$selectedOwned = in_array($oldDomain, $ownedDomainHosts, true) ? $oldDomain : '';
$externalFallback = ! in_array($oldDomain, $ownedDomainHosts, true) ? $oldDomain : '';
@endphp
<script>if (window !== window.top) window.top.location.href = window.location.href;</script>
<div class="space-y-6" x-data="{
domainModal: {{ $formErrors->has('domain') || $formErrors->has('onboarding_mode') || $formErrors->has('document_root') ? 'true' : 'false' }},
panelModal: false,
renewModal: false,
selectedDuration: null,
showSheet: false,
checkoutUrl: '',
renewLoading: false,
async submitRenewal() {
this.renewLoading = true;
try {
const res = await fetch('{{ route('hosting.accounts.renew', $account) }}', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
body: JSON.stringify({ duration_months: this.selectedDuration }),
});
const data = await res.json();
if (!res.ok || data.error) { alert(data.error || 'Unable to start payment.'); this.renewLoading = false; return; }
this.renewModal = false;
this.checkoutUrl = data.checkout_url;
this.showSheet = true;
this.renewLoading = false;
} catch(e) {
alert('Network error. Please try again.');
this.renewLoading = false;
}
}
}">
{{-- Breadcrumb --}}
<nav class="flex items-center gap-1.5 text-sm text-slate-500">
<a href="{{ route('hosting.single-domain') }}" class="hover:text-slate-700 transition">Hosting</a>
<svg class="h-3.5 w-3.5 text-slate-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5m0 0v-15"/></svg>
<span class="font-medium text-slate-800">{{ $accountLabel ?? ($account->primary_domain ?: $account->username) }}</span>
</nav>
{{-- Expired Account Banner --}}
@if ($account->isExpired() && $account->isInGracePeriod())
<div class="rounded-xl border border-red-200 bg-red-50 px-5 py-4">
<div class="flex items-start gap-3">
<svg class="h-5 w-5 shrink-0 text-red-500 mt-0.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"/></svg>
<div>
<h3 class="text-sm font-semibold text-red-900">Hosting Account Expired</h3>
<p class="text-sm text-red-700 mt-0.5">Your hosting expired on <span class="font-medium">{{ $account->expires_at->format('M d, Y') }}</span>. Your site is offline but your files are safe. Renew before <span class="font-medium">{{ $account->gracePeriodEndsAt()->format('M d, Y') }}</span> to avoid automatic deletion.</p>
</div>
</div>
</div>
@endif
{{-- Pending Setup Banner --}}
@if ($account->status === 'pending')
<div class="rounded-xl border border-amber-200 bg-amber-50 px-5 py-4">
<div class="flex items-start gap-3">
<svg class="h-5 w-5 shrink-0 text-amber-500 mt-0.5 animate-spin" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992V4.356m-.984 13.287A9 9 0 1 1 19.07 7.93l1.945 1.418"/></svg>
<div>
<h3 class="text-sm font-semibold text-amber-900">Setting Up Your Hosting</h3>
<p class="text-sm text-amber-700 mt-0.5">Your hosting account is being set up on our servers. This usually takes 1-2 minutes. Please refresh this page shortly to check the status.</p>
</div>
</div>
</div>
@endif
{{-- Provisioning Banner --}}
@if ($account->status === 'provisioning')
<div class="rounded-xl border border-blue-200 bg-blue-50 px-5 py-4">
<div class="flex items-start gap-3">
<svg class="h-5 w-5 shrink-0 text-blue-500 mt-0.5 animate-spin" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992V4.356m-.984 13.287A9 9 0 1 1 19.07 7.93l1.945 1.418"/></svg>
<div>
<h3 class="text-sm font-semibold text-blue-900">Provisioning In Progress</h3>
<p class="text-sm text-blue-700 mt-0.5">We're creating your hosting environment, setting up PHP, and configuring your server. This typically takes 1-3 minutes. Please refresh this page to check progress.</p>
</div>
</div>
</div>
@endif
{{-- Failed Setup Banner --}}
@if ($account->status === 'failed')
<div class="rounded-xl border border-red-200 bg-red-50 px-5 py-4">
<div class="flex items-start gap-3">
<svg class="h-5 w-5 shrink-0 text-red-500 mt-0.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"/></svg>
<div>
<h3 class="text-sm font-semibold text-red-900">Setup Failed</h3>
<p class="text-sm text-red-700 mt-0.5">There was a problem setting up your hosting account. Our team has been notified and will resolve this shortly. If you need immediate assistance, please <a href="{{ ladill_account_url('/support-tickets/create') }}" class="underline font-medium hover:text-red-800">open a support ticket</a>.</p>
</div>
</div>
</div>
@endif
{{-- Hero Card --}}
<div class="relative overflow-hidden rounded-2xl border border-slate-200 bg-white">
<div class="absolute inset-0 bg-gradient-to-br from-indigo-50/60 via-white to-slate-50/40"></div>
<div class="relative flex flex-wrap items-start justify-between gap-4 px-6 py-6 sm:px-8 sm:py-7">
<div class="flex items-start gap-4">
<div class="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-indigo-100 text-indigo-600">
<svg class="h-7 w-7" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 9l9-6 9 6m-1.5 12V10.332A48.36 48.36 0 0 0 12 9.75c-2.551 0-5.056.2-7.5.582V21M3 21h18M12 6.75h.008v.008H12V6.75Z"/>
</svg>
</div>
<div>
<h2 class="text-xl font-bold tracking-tight text-slate-900">Hosting Account</h2>
<div class="mt-1 flex flex-wrap items-center gap-2.5">
<span class="text-sm text-slate-700">{{ $accountLabel ?? ($account->primary_domain ?: $account->username) }}</span>
<span class="rounded-full px-2.5 py-0.5 text-[11px] font-semibold {{ $statusColors[$account->status ?? 'pending'] }}">
{{ $statusLabels[$account->status ?? 'Pending'] }}
</span>
<span class="rounded-full px-2.5 py-0.5 text-[11px] font-semibold {{ $resourceStatusColors[$account->resource_status ?? 'active'] }}">
{{ ucfirst($account->resource_status ?? 'active') }} resources
</span>
</div>
<p class="mt-1.5 text-xs text-slate-500">Created {{ $account->created_at->format('M d, Y') }}
@if ($account->expires_at) &middot; Expires {{ $account->expires_at->format('M d, Y') }} @endif
</p>
</div>
</div>
<div class="flex flex-wrap items-center gap-2">
@if ($account->assignedDurationMonths() && !empty($renewalOptions) && ($account->isExpired() || $account->expires_at?->isBefore(now()->addDays(30))))
<button @click="renewModal = true; selectedDuration = {{ collect($renewalOptions)->firstWhere('current', true)['months'] ?? $account->assignedDurationMonths() }}" type="button"
class="inline-flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition">
<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="M16.023 9.348h4.992V4.356m-.984 13.287A9 9 0 1 1 19.07 7.93l1.945 1.418"/></svg>
Renew Plan
</button>
@endif
<button @click="panelModal = true" type="button"
class="btn-primary">
<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="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/></svg>
Control Panel
</button>
@if ($canLinkMoreDomains)
<button @click="domainModal = true" type="button"
class="inline-flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition">
<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>
Link Domain
</button>
@endif
</div>
</div>
</div>
@if (!empty($upsellRecommendations))
<div class="rounded-2xl border border-amber-200 bg-gradient-to-r from-amber-50 via-white to-white">
<div class="flex items-center justify-between gap-3 border-b border-amber-100 px-5 py-4">
<div>
<h3 class="text-sm font-semibold text-slate-900">Recommended Next Steps</h3>
<p class="mt-0.5 text-xs text-slate-500">These suggestions are based on how this account is being used right now.</p>
</div>
<span class="rounded-full bg-amber-100 px-2.5 py-1 text-[11px] font-semibold text-amber-700">{{ count($upsellRecommendations) }} suggestion{{ count($upsellRecommendations) === 1 ? '' : 's' }}</span>
</div>
<div class="grid gap-4 px-5 py-4 lg:grid-cols-2">
@foreach ($upsellRecommendations as $recommendation)
<div class="rounded-xl border border-amber-100 bg-white p-4">
<div class="flex items-start justify-between gap-3">
<div>
<p class="text-sm font-semibold text-slate-900">{{ $recommendation['title'] }}</p>
<p class="mt-1 text-xs leading-5 text-slate-500">{{ $recommendation['message'] }}</p>
</div>
<span class="rounded-full bg-slate-100 px-2.5 py-1 text-[11px] font-semibold text-slate-700">
{{ $recommendation['type'] === 'plan_upgrade' ? 'Upgrade' : 'Add-on' }}
</span>
</div>
@if (($recommendation['type'] ?? null) === 'plan_upgrade' && !empty($recommendation['target_product_id']))
<form method="POST" action="{{ route('hosting.accounts.plan-change', $account) }}" class="mt-4">
@csrf
<input type="hidden" name="target_product_id" value="{{ $recommendation['target_product_id'] }}">
<button type="submit" class="inline-flex items-center rounded-lg bg-slate-900 px-3.5 py-2 text-xs font-semibold text-white transition hover:bg-slate-800">
{{ $recommendation['cta_label'] }}
</button>
</form>
@elseif (!empty($recommendation['cta_url']))
<div class="mt-4">
<a href="{{ $recommendation['cta_url'] }}" class="inline-flex items-center rounded-lg bg-slate-900 px-3.5 py-2 text-xs font-semibold text-white transition hover:bg-slate-800">
{{ $recommendation['cta_label'] }}
</a>
</div>
@endif
</div>
@endforeach
</div>
</div>
@endif
{{-- Info Cards Grid --}}
<div class="grid grid-cols-1 gap-5 lg:grid-cols-4">
{{-- Account Details Card --}}
<div class="rounded-2xl border border-slate-200 bg-white">
<div class="flex items-center gap-2.5 border-b border-slate-100 px-5 py-4">
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-blue-50 text-blue-600">
<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="M3 9l9-6 9 6m-1.5 12V10.332A48.36 48.36 0 0 0 12 9.75c-2.551 0-5.056.2-7.5.582V21M3 21h18M12 6.75h.008v.008H12V6.75Z"/></svg>
</div>
<h3 class="text-sm font-semibold text-slate-900">Account Details</h3>
</div>
<div class="px-5 py-4 space-y-3">
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">Username</span>
<span class="text-sm font-medium text-slate-800">{{ $account->username }}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">Node IP</span>
<span class="text-sm font-medium text-slate-800">{{ $account->node?->ip_address ?? '161.97.138.149' }}</span>
</div>
@if ($account->product)
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">Product</span>
<span class="text-sm font-medium text-slate-800">{{ $account->product->name }}</span>
</div>
@endif
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">Email Usage</span>
<span class="text-sm font-medium text-slate-800">
{{ $emailUsage['mailbox_count'] }}
@if ($emailUsage['free_allowance'] === null)
/ Unlimited included
@else
/ {{ $emailUsage['free_allowance'] }} included
@endif
</span>
</div>
@if ($emailUsage['extra_count'] > 0)
<div class="rounded-lg bg-amber-50 px-3 py-2 text-xs text-amber-700">
Extra Emails: {{ $emailUsage['extra_count'] }} x GHS 5 = GHS {{ number_format($emailUsage['extra_total'], 2) }}
</div>
@endif
</div>
</div>
{{-- Resources Card --}}
<div class="rounded-2xl border border-slate-200 bg-white">
<div class="flex items-center gap-2.5 border-b border-slate-100 px-5 py-4">
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-violet-50 text-violet-600">
<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="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Z"/></svg>
</div>
<h3 class="text-sm font-semibold text-slate-900">Resources</h3>
</div>
<div class="px-5 py-4 space-y-3">
@php
$diskLimit = max(($account->allocated_disk_gb ?: ($account->product?->disk_gb ?? 10)), 1) * 1073741824;
$bandwidthLimit = $account->resource_limits['bandwidth_limit_bytes'] ?? 107374182400; // 100GB default
$diskUsed = $account->disk_used_bytes ?? 0;
$bandwidthUsed = $account->bandwidth_used_bytes ?? 0;
$diskPercent = $diskLimit > 0 ? ($diskUsed / $diskLimit) * 100 : 0;
$bandwidthPercent = $bandwidthLimit > 0 ? ($bandwidthUsed / $bandwidthLimit) * 100 : 0;
@endphp
<div>
<div class="flex items-center justify-between mb-1">
<span class="text-xs text-slate-500">Disk Space</span>
<span class="text-xs text-slate-500">{{ number_format($diskUsed / 1073741824, 1) }} GB / {{ number_format($diskLimit / 1073741824, 1) }} GB</span>
</div>
<div class="w-full rounded-full bg-slate-200 h-1.5">
<div class="rounded-full bg-gradient-to-r from-blue-500 to-blue-600 h-1.5" style="width: {{ min($diskPercent, 100) }}%"></div>
</div>
</div>
<div>
<div class="flex items-center justify-between mb-1">
<span class="text-xs text-slate-500">Bandwidth</span>
<span class="text-xs text-slate-500">{{ number_format($bandwidthUsed / 1073741824, 1) }} GB / {{ number_format($bandwidthLimit / 1073741824, 1) }} GB</span>
</div>
<div class="w-full rounded-full bg-slate-200 h-1.5">
<div class="rounded-full bg-gradient-to-r from-purple-500 to-purple-600 h-1.5" style="width: {{ min($bandwidthPercent, 100) }}%"></div>
</div>
</div>
@if ($account->php_version)
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">PHP Version</span>
<span class="text-xs font-medium text-slate-800">{{ $account->php_version }}</span>
</div>
@endif
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">CPU</span>
<span class="text-xs font-medium text-slate-800">{{ number_format((float) ($account->cpu_usage_percent ?? 0), 1) }}% / {{ $account->cpu_limit_percent ?? '-' }}%</span>
</div>
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">Memory</span>
<span class="text-xs font-medium text-slate-800">{{ $account->memory_used_mb ?? 0 }} MB / {{ $account->memory_limit_mb ?? '-' }} MB</span>
</div>
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">Processes</span>
<span class="text-xs font-medium text-slate-800">{{ $account->process_count ?? 0 }} / {{ $account->process_limit ?? '-' }}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">Inodes</span>
<span class="text-xs font-medium text-slate-800">{{ number_format((int) ($account->inode_count ?? 0)) }} / {{ number_format((int) ($account->inode_limit ?? 0)) }}</span>
</div>
</div>
</div>
{{-- Features Card --}}
<div class="rounded-2xl border border-slate-200 bg-white">
<div class="flex items-center gap-2.5 border-b border-slate-100 px-5 py-4">
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-50 text-emerald-600">
<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="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .582.416l5.135.1a.564.564 0 0 1 .314.988l-3.74 3.247a.563.563 0 0 0-.182.613l1.28 3.982a.562.562 0 0 1-.84.61l-4.652-2.52a.562.562 0 0 0-.6 0L6.68 19.03a.562.562 0 0 1-.84-.61l1.28-3.982a.562.562 0 0 0-.182-.613l-3.74-3.247a.562.562 0 0 1 .314-.988l5.135-.1a.562.562 0 0 0 .582-.416l2.125-5.11Z"/></svg>
</div>
<h3 class="text-sm font-semibold text-slate-900">Features</h3>
</div>
<div class="px-5 py-4 space-y-2">
@php
$features = $account->features_enabled ?? [];
$defaultFeatures = ['SFTP Access', 'FTP Access', 'Email Accounts', 'MySQL Database'];
$displayFeatures = !empty($features) ? $features : $defaultFeatures;
@endphp
@foreach ($displayFeatures as $feature)
<div class="flex items-center gap-2">
<div class="h-1.5 w-1.5 rounded-full bg-emerald-500"></div>
<span class="text-xs text-slate-700">{{ $feature }}</span>
</div>
@endforeach
</div>
</div>
{{-- Health Card --}}
<div class="rounded-2xl border border-slate-200 bg-white">
<div class="flex items-center gap-2.5 border-b border-slate-100 px-5 py-4">
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-50 text-emerald-600">
<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="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z"/></svg>
</div>
<h3 class="text-sm font-semibold text-slate-900">Health</h3>
</div>
<div class="px-5 py-4 space-y-3">
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">Status</span>
<span class="text-xs font-semibold {{ $account->status === 'active' ? 'text-emerald-600' : ($account->status === 'suspended' ? 'text-red-600' : 'text-amber-600') }}">
{{ ucfirst($account->status ?? 'Unknown') }}
</span>
</div>
@if ($account->provisioned_at)
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">Provisioned</span>
<span class="text-xs text-slate-600">{{ $account->provisioned_at->format('M d, Y') }}</span>
</div>
@endif
@if ($account->suspended_at)
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">Suspended</span>
<span class="text-xs text-slate-600">{{ $account->suspended_at->format('M d, Y') }}</span>
</div>
@endif
@if (!empty($account->suspension_reason))
<div class="text-xs text-red-600 bg-red-50 rounded px-2 py-1">
{{ $account->suspension_reason }}
</div>
@endif
@if ($account->uploads_restricted)
<div class="text-xs text-amber-700 bg-amber-50 rounded px-2 py-1">
Uploads are temporarily restricted because the inode limit has been reached.
</div>
@endif
</div>
</div>
</div>
{{-- Domain Card --}}
<div class="rounded-2xl border border-slate-200 bg-white">
<div class="flex items-center justify-between border-b border-slate-100 px-5 py-4">
<div class="flex items-center gap-2.5">
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-blue-50 text-blue-600">
<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="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244"/></svg>
</div>
<h3 class="text-sm font-semibold text-slate-900">Domains</h3>
</div>
@if ($canLinkMoreDomains)
<button @click="domainModal = true" type="button" class="text-xs font-semibold text-indigo-600 hover:text-indigo-800 transition">+ Add</button>
@endif
</div>
<div class="px-5 py-4">
@if ($linkedSites->isNotEmpty())
<div class="space-y-3">
@foreach ($linkedSites as $site)
<div class="flex items-start justify-between gap-3 rounded-xl border border-slate-100 bg-slate-50/70 px-4 py-3">
<div>
<div class="flex flex-wrap items-center gap-2">
<span class="text-sm font-medium text-slate-800">{{ $site->domain }}</span>
@if ($site->type === 'primary')
<span class="rounded-full bg-blue-100 px-2 py-0.5 text-[11px] font-semibold text-blue-700">Primary</span>
@endif
@if ($site->ssl_enabled)
<span class="rounded-full bg-emerald-100 px-2 py-0.5 text-[11px] font-semibold text-emerald-700">SSL</span>
@endif
</div>
<p class="mt-1 text-xs text-slate-500">{{ $site->document_root }}</p>
</div>
<span class="rounded-full px-2 py-0.5 text-[11px] font-semibold {{ $site->status === 'active' ? 'bg-emerald-100 text-emerald-700' : 'bg-amber-100 text-amber-700' }}">
{{ ucfirst($site->status) }}
</span>
</div>
@endforeach
</div>
<p class="mt-3 text-xs text-slate-400">
@if ($maxDomains === -1)
{{ $account->domainSitesCount() }} domain {{ $account->domainSitesCount() === 1 ? 'slot' : 'slots' }} in use (unlimited).
@else
{{ $account->domainSitesCount() }} of {{ $maxDomains }} domain {{ $maxDomains === 1 ? 'slot' : 'slots' }} in use.
@endif
· {{ $account->subdomainSitesCount() }}{{ $account->maxSubdomainsLimit() === -1 ? '' : ' of '.$account->maxSubdomainsLimit() }} subdomain {{ $account->subdomainSitesCount() === 1 ? 'slot' : 'slots' }} in use.
</p>
@elseif ($account->primary_domain && ! \App\Services\Hosting\PlaceholderPrimaryDomainService::isPlaceholderDomain($account->primary_domain))
<div class="flex items-center justify-between">
<span class="text-sm font-medium text-slate-800">{{ $account->primary_domain }}</span>
<span class="rounded-full px-2 py-0.5 text-[11px] font-semibold bg-emerald-100 text-emerald-700">
Active
</span>
</div>
<p class="mt-2 text-xs text-slate-400">Primary domain for this hosting account.</p>
@else
<div class="flex flex-col items-center py-4 text-center">
<p class="text-sm text-slate-500">No domain linked</p>
<p class="mt-0.5 text-xs text-slate-400">Link a domain to use your hosting account.</p>
@if ($canLinkMoreDomains)
<button @click="domainModal = true" type="button" class="mt-2.5 text-xs font-semibold text-indigo-600 hover:text-indigo-800 transition">Connect a domain</button>
@endif
</div>
@endif
</div>
</div>
{{-- Domain Linking Modal --}}
<template x-teleport="body">
<div x-show="domainModal" x-cloak class="fixed inset-0 z-50 overflow-y-auto sm:flex sm:items-center sm:justify-center sm:p-4" @keydown.escape.window="domainModal = false">
<div x-show="domainModal" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm hidden sm:block" @click="domainModal = false"></div>
<div x-show="domainModal" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
class="relative min-h-full sm:min-h-0 w-full sm:max-w-lg sm:rounded-2xl sm:border sm:border-slate-200 bg-white shadow-xl" @click.stop>
<form method="POST" action="{{ $addDomainAction }}"
x-data="ladillDomainSourceForm({
domainSource: @js($initialDomainSource),
selectedOwnedDomain: @js($selectedOwned),
externalDomain: @js($externalFallback),
ownedDomains: @js($ownedDomainHosts),
ownedUrl: @js(route('hosting.domains.owned', ['exclude' => $linkedSites->pluck('domain')->filter()->all()])),
onboardingMode: @js(old('onboarding_mode', 'ns_auto')),
})">
@csrf
<div class="flex items-center justify-between border-b border-slate-100 px-6 py-4">
<h3 class="text-base font-semibold text-slate-900">Connect a Domain</h3>
<button @click="domainModal = false" type="button" class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition">
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/></svg>
</button>
</div>
<div class="p-6 space-y-5">
<x-domain-source-fields
:owned-domains="$ownedDomainHosts"
owned-url="{{ route('hosting.domains.owned') }}"
show-connection-method
/>
<div class="flex items-center justify-end gap-3 pt-1">
<button @click="domainModal = false" type="button" class="rounded-xl border border-slate-200 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition">Cancel</button>
<button type="submit" class="btn-primary">Add Domain</button>
</div>
</div>
</form>
</div>
</div>
</template>
{{-- Renewal Duration Modal --}}
@if (!empty($renewalOptions))
<template x-teleport="body">
<div x-show="renewModal" x-cloak class="fixed inset-0 z-50 overflow-y-auto sm:flex sm:items-center sm:justify-center sm:p-4" @keydown.escape.window="renewModal = false">
<div x-show="renewModal" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm hidden sm:block" @click="renewModal = false"></div>
<div x-show="renewModal" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
class="relative min-h-full sm:min-h-0 w-full sm:max-w-md sm:rounded-2xl sm:border sm:border-slate-200 bg-white shadow-xl" @click.stop>
<div>
<div class="flex items-center justify-between border-b border-slate-100 px-6 py-4">
<div>
<h3 class="text-base font-semibold text-slate-900">Renew Hosting Plan</h3>
<p class="mt-0.5 text-xs text-slate-500">{{ $accountLabel ?? ($account->primary_domain ?: $account->username) }}</p>
</div>
<button @click="renewModal = false" type="button" class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition">
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/></svg>
</button>
</div>
<div class="p-6 space-y-4">
<p class="text-sm text-slate-600">Choose a renewal duration:</p>
<div class="space-y-2">
@foreach ($renewalOptions as $option)
<label class="flex cursor-pointer items-center justify-between gap-3 rounded-xl border p-4 transition"
:class="selectedDuration === {{ $option['months'] }} ? 'border-indigo-300 bg-indigo-50/50 ring-1 ring-indigo-200' : 'border-slate-200 hover:border-slate-300'">
<div class="flex items-center gap-3">
<input type="radio" name="duration_radio" value="{{ $option['months'] }}"
x-model.number="selectedDuration"
class="text-indigo-600 focus:ring-indigo-500">
<div>
<p class="text-sm font-medium text-slate-900">{{ $option['label'] }}</p>
@if ($option['current'])
<p class="text-[11px] text-indigo-600 font-medium">Current plan term</p>
@endif
</div>
</div>
<span class="text-sm font-semibold text-slate-800">{{ $option['currency'] }} {{ number_format($option['amount'], 2) }}</span>
</label>
@endforeach
</div>
@if ($account->expires_at)
<div class="rounded-lg bg-slate-50 px-3 py-2 text-xs text-slate-500">
Current expiry: <span class="font-medium text-slate-700">{{ $account->expires_at->format('M d, Y') }}</span>
</div>
@endif
</div>
<div class="flex items-center justify-end gap-3 border-t border-slate-100 px-6 py-4">
<button @click="renewModal = false" type="button" class="rounded-xl border border-slate-200 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition">Cancel</button>
<button type="button" @click="submitRenewal()" :disabled="!selectedDuration || renewLoading"
class="btn-primary disabled:opacity-50 disabled:cursor-not-allowed">
<svg x-show="renewLoading" x-cloak class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
<svg x-show="!renewLoading" 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="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Z"/></svg>
<span x-text="renewLoading ? 'Processing…' : 'Proceed to Payment'">Proceed to Payment</span>
</button>
</div>
</div>
</div>
</div>
</template>
@endif
@include('partials.paystack-sheet')
{{-- Control Panel Modal --}}
<template x-teleport="body">
<div x-show="panelModal" x-cloak class="fixed inset-0 z-50 overflow-y-auto sm:flex sm:items-center sm:justify-center sm:p-4" @keydown.escape.window="panelModal = false">
<div x-show="panelModal" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm hidden sm:block" @click="panelModal = false"></div>
<div x-show="panelModal" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
class="relative min-h-full sm:min-h-0 w-full sm:max-w-lg sm:rounded-2xl sm:border sm:border-slate-200 bg-white shadow-xl" @click.stop>
<div class="flex items-center justify-between border-b border-slate-100 px-6 py-4">
<h3 class="text-base font-semibold text-slate-900">Control Panel Access</h3>
<button @click="panelModal = false" type="button" class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition">
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/></svg>
</button>
</div>
<div class="p-6 space-y-5">
<div class="rounded-xl border border-slate-200 bg-slate-50 p-4">
<p class="text-sm font-medium text-slate-900 mb-4">SFTP Access Details:</p>
<div class="space-y-4">
<div class="flex items-center justify-between">
<span class="text-sm font-medium text-slate-700">Host:</span>
<span class="text-sm font-mono text-slate-600">{{ $account->node?->ip_address ?? '161.97.138.149' }}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm font-medium text-slate-700">Port:</span>
<span class="text-sm font-mono text-slate-600">22</span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm font-medium text-slate-700">Username:</span>
<span class="text-sm font-mono text-slate-600">{{ $account->username }}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm font-medium text-slate-700">Password:</span>
<span class="text-sm text-slate-500">Set in Control Panel Settings</span>
</div>
</div>
</div>
<div class="grid gap-3">
<a href="{{ $panelIndexUrl }}" class="btn-primary">
<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="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
Open Control Panel
</a>
<a href="{{ $panelSettingsUrl }}" class="inline-flex items-center justify-center gap-2 rounded-xl border border-slate-200 bg-white px-5 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition">
<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="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z"/></svg>
Change SFTP Password
</a>
</div>
</div>
</div>
</div>
</template>
</div>
</x-user-layout>