From d05932105d499ff23c4612b630e8d5fd32b625f9 Mon Sep 17 00:00:00 2001 From: nexxo Date: Sat, 25 Jul 2026 19:14:42 +0200 Subject: [PATCH] feat(portal): bind the cloud card to the real instance (plan/seats/domain/quota) so the maintenance badge matches it Co-Authored-By: Claude Opus 4.8 --- app/Livewire/Cloud.php | 35 +++++++++++++++++++++----------- lang/de/cloud.php | 1 + lang/en/cloud.php | 1 + tests/Feature/PortalTabsTest.php | 16 +++++++++++++++ 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/app/Livewire/Cloud.php b/app/Livewire/Cloud.php index 296fb9e..6da4007 100644 --- a/app/Livewire/Cloud.php +++ b/app/Livewire/Cloud.php @@ -18,28 +18,39 @@ class Cloud extends Component $locale = app()->getLocale(); $growth = [180, 188, 195, 199, 204, 210, 214, 219, 223, 228, 231, 235]; - // Maintenance affecting THE DISPLAYED instance's host — scoped to that one - // instance so a customer with several instances never sees another - // instance's window on this card. + // The card is built from the customer's real service instance, so the + // maintenance badge below can be scoped to exactly that instance's host. $customer = $this->customer(); $shown = $customer?->instances() ->whereIn('status', ['active', 'provisioning', 'cancellation_scheduled']) ->latest('id')->first(); $maintenance = MaintenanceWindow::forInstance($shown)->first(); + $plans = (array) config('provisioning.plans'); + $planKey = $shown->plan ?? 'team'; + $plan = $plans[$planKey] ?? []; + $quota = (int) ($shown->quota_gb ?? ($plan['quota_gb'] ?? 500)); + // Usage metering is not wired yet — show the fixture curve until it is. + $used = min($growth[count($growth) - 1], $quota); + $domain = $shown?->custom_domain + ?: ($shown?->subdomain ? $shown->subdomain.'.'.config('provisioning.dns.zone', 'clupilot.com') : 'cloud.example.com'); + return view('livewire.cloud', [ 'instance' => [ - 'name' => 'Cloud der Kanzlei Berger', - 'domain' => 'cloud.kanzlei-berger.at', - 'status' => 'active', - 'plan' => __('cloud.plan_line', ['plan' => 'CluPilot Cloud Team', 'price' => 179]), + 'name' => $customer ? __('cloud.instance_name', ['name' => $customer->name]) : __('cloud.title'), + 'domain' => $domain, + 'status' => $shown->status ?? 'active', + 'plan' => __('cloud.plan_line', [ + 'plan' => 'CluPilot Cloud '.__('billing.plan.'.$planKey), + 'price' => (int) round(($plan['price_cents'] ?? 0) / 100), + ]), 'location' => __('cloud.datacenter'), 'version' => 'Nextcloud 31.0.4', 'php' => 'PHP 8.3 · MariaDB 11.4', - 'storageUsed' => 235, - 'storageQuota' => 500, - 'seats' => __('billing.seats_count', ['count' => 25]), - 'performance' => __('billing.perf.enhanced'), + 'storageUsed' => $used, + 'storageQuota' => $quota, + 'seats' => __('billing.seats_count', ['count' => $plan['seats'] ?? 0]), + 'performance' => __('billing.perf.'.($plan['performance'] ?? 'standard')), ], 'storageChart' => [ 'type' => 'line', @@ -64,7 +75,7 @@ class Cloud extends Component 'plugins' => ['legend' => ['display' => false]], ], ], - 'storageLabel' => Number::format(235, locale: $locale).' / '.Number::format(500, locale: $locale).' GB', + 'storageLabel' => Number::format($used, locale: $locale).' / '.Number::format($quota, locale: $locale).' GB', 'maintenance' => $maintenance, ]); } diff --git a/lang/de/cloud.php b/lang/de/cloud.php index c2b4686..2f9a659 100644 --- a/lang/de/cloud.php +++ b/lang/de/cloud.php @@ -14,6 +14,7 @@ return [ 'current' => 'aktuell', 'runtime' => 'Laufzeit', 'seats' => 'Benutzer', + 'instance_name' => 'Cloud der :name', 'maintenance_planned' => 'Wartung geplant', 'maintenance_active' => 'Wartung läuft', 'maintenance_window' => 'Zeitraum: :start bis :end.', diff --git a/lang/en/cloud.php b/lang/en/cloud.php index 644918a..85ea397 100644 --- a/lang/en/cloud.php +++ b/lang/en/cloud.php @@ -14,6 +14,7 @@ return [ 'current' => 'up to date', 'runtime' => 'Runtime', 'seats' => 'Users', + 'instance_name' => ':name cloud', 'maintenance_planned' => 'Maintenance planned', 'maintenance_active' => 'Maintenance in progress', 'maintenance_window' => 'Window: :start to :end.', diff --git a/tests/Feature/PortalTabsTest.php b/tests/Feature/PortalTabsTest.php index 4ce4fb0..801a63d 100644 --- a/tests/Feature/PortalTabsTest.php +++ b/tests/Feature/PortalTabsTest.php @@ -15,3 +15,19 @@ it('renders portal tabs for authenticated users', function (string $tab) { ->assertOk() ->assertSee('CluPilot'); })->with($tabs); + +it('binds the cloud card to the real instance', function () { + $user = \App\Models\User::factory()->create(['email' => 'c@cloud.test']); + $customer = \App\Models\Customer::factory()->create(['email' => 'c@cloud.test', 'user_id' => $user->id, 'name' => 'Acme AG']); + $host = \App\Models\Host::factory()->active()->create(); + \App\Models\Instance::factory()->create([ + 'customer_id' => $customer->id, 'host_id' => $host->id, 'status' => 'active', + 'plan' => 'business', 'subdomain' => 'acme-ag', 'quota_gb' => 1000, + ]); + + $this->actingAs($user)->get(route('cloud')) + ->assertOk() + ->assertSee('acme-ag.'.config('provisioning.dns.zone')) // real subdomain, not a fixture + ->assertSee(__('billing.plan.business')) + ->assertSee(__('billing.perf.high')); +});