diff --git a/app/Actions/StartCustomerProvisioning.php b/app/Actions/StartCustomerProvisioning.php
index b611b23..c1f7b01 100644
--- a/app/Actions/StartCustomerProvisioning.php
+++ b/app/Actions/StartCustomerProvisioning.php
@@ -46,7 +46,9 @@ class StartCustomerProvisioning
'pipeline' => 'customer',
'status' => ProvisioningRun::STATUS_PENDING,
'current_step' => 0,
- 'context' => [],
+ // Snapshot resolved branding so retries apply identical inputs
+ // (unset customer → CluPilot defaults, resolved once here).
+ 'context' => ['branding' => $customer->brandingResolved()],
]);
return [$order, $run];
diff --git a/app/Livewire/ConfirmCancelPackage.php b/app/Livewire/ConfirmCancelPackage.php
new file mode 100644
index 0000000..55eaace
--- /dev/null
+++ b/app/Livewire/ConfirmCancelPackage.php
@@ -0,0 +1,63 @@
+customer();
+ $instance = $customer?->instances()->latest('id')->first();
+
+ if ($instance === null || $instance->status === 'cancellation_scheduled') {
+ return $this->redirectRoute('settings', navigate: true);
+ }
+
+ // Typed confirmation must match the instance subdomain.
+ if (trim($this->confirmName) !== (string) $instance->subdomain) {
+ $this->addError('confirmName', __('settings.cancel_mismatch'));
+
+ return;
+ }
+
+ $instance->update([
+ 'status' => 'cancellation_scheduled',
+ 'cancel_requested_at' => now(),
+ 'service_ends_at' => now()->endOfMonth(), // end of the billing term
+ ]);
+
+ return $this->redirectRoute('settings', navigate: true);
+ }
+
+ private function customer(): ?Customer
+ {
+ $user = auth()->user();
+ if (! $user) {
+ return null;
+ }
+
+ return Customer::query()->where('user_id', $user->id)->first()
+ ?? Customer::query()->where('email', $user->email)->first();
+ }
+
+ public function render()
+ {
+ $instance = $this->customer()?->instances()->latest('id')->first();
+
+ return view('livewire.confirm-cancel-package', [
+ 'subdomain' => $instance?->subdomain ?? '',
+ ]);
+ }
+}
diff --git a/app/Livewire/ConfirmCloseAccount.php b/app/Livewire/ConfirmCloseAccount.php
new file mode 100644
index 0000000..79a4c33
--- /dev/null
+++ b/app/Livewire/ConfirmCloseAccount.php
@@ -0,0 +1,67 @@
+customer();
+ if ($customer === null) {
+ return $this->redirectRoute('settings', navigate: true);
+ }
+
+ if ($this->hasActivePackage($customer)) {
+ $this->addError('confirmWord', __('settings.close_blocked'));
+
+ return;
+ }
+
+ if (mb_strtoupper(trim($this->confirmWord)) !== __('settings.close_keyword')) {
+ $this->addError('confirmWord', __('settings.close_mismatch'));
+
+ return;
+ }
+
+ $customer->update(['closed_at' => now(), 'status' => 'closed']);
+
+ return $this->redirectRoute('settings', navigate: true);
+ }
+
+ private function hasActivePackage(Customer $customer): bool
+ {
+ return $customer->instances()
+ ->whereNotIn('status', ['cancelled', 'deprovisioned'])
+ ->exists();
+ }
+
+ private function customer(): ?Customer
+ {
+ $user = auth()->user();
+ if (! $user) {
+ return null;
+ }
+
+ return Customer::query()->where('user_id', $user->id)->first()
+ ?? Customer::query()->where('email', $user->email)->first();
+ }
+
+ public function render()
+ {
+ $customer = $this->customer();
+
+ return view('livewire.confirm-close-account', [
+ 'blocked' => $customer !== null && $this->hasActivePackage($customer),
+ ]);
+ }
+}
diff --git a/app/Livewire/Settings.php b/app/Livewire/Settings.php
new file mode 100644
index 0000000..b5df2e3
--- /dev/null
+++ b/app/Livewire/Settings.php
@@ -0,0 +1,164 @@
+customer();
+ if ($c === null) {
+ return;
+ }
+ $this->companyName = $c->name ?? '';
+ $this->contactName = $c->contact_name ?? '';
+ $this->phone = $c->phone ?? '';
+ $this->vatId = $c->vat_id ?? '';
+ $this->billingAddress = $c->billing_address ?? '';
+ $this->brandDisplayName = $c->brand_display_name ?? '';
+ $this->brandPrimary = $c->brand_primary_color ?? '';
+ $this->brandAccent = $c->brand_accent_color ?? '';
+ $this->brandLogoPath = $c->brand_logo_path;
+ }
+
+ public function saveProfile(): void
+ {
+ $c = $this->customer();
+ if ($c === null) {
+ return;
+ }
+
+ $this->validateOnly('companyName');
+ $data = $this->validate([
+ 'companyName' => 'required|string|max:255',
+ 'contactName' => 'nullable|string|max:255',
+ 'phone' => 'nullable|string|max:64',
+ 'vatId' => 'nullable|string|max:64',
+ 'billingAddress' => 'nullable|string|max:2000',
+ ]);
+
+ $c->update([
+ 'name' => $data['companyName'],
+ 'contact_name' => $data['contactName'] ?: null,
+ 'phone' => $data['phone'] ?: null,
+ 'vat_id' => $data['vatId'] ?: null,
+ 'billing_address' => $data['billingAddress'] ?: null,
+ ]);
+
+ $this->dispatch('notify', message: __('settings.profile_saved'));
+ }
+
+ public function saveBranding(): void
+ {
+ $c = $this->customer();
+ if ($c === null) {
+ return;
+ }
+
+ $this->validate([
+ 'brandDisplayName' => 'nullable|string|max:255',
+ 'brandPrimary' => 'nullable|regex:/^#[0-9a-fA-F]{6}$/',
+ 'brandAccent' => 'nullable|regex:/^#[0-9a-fA-F]{6}$/',
+ 'logo' => 'nullable|image|mimes:png,webp|max:2048',
+ ]);
+
+ if ($this->logo !== null) {
+ // Replace any previous logo; validated MIME/size above.
+ if ($this->brandLogoPath !== null) {
+ Storage::disk('public')->delete($this->brandLogoPath);
+ }
+ $this->brandLogoPath = $this->logo->store('branding', 'public');
+ $this->logo = null;
+ }
+
+ $c->update([
+ 'brand_display_name' => $this->brandDisplayName ?: null,
+ 'brand_primary_color' => $this->brandPrimary ?: null,
+ 'brand_accent_color' => $this->brandAccent ?: null,
+ 'brand_logo_path' => $this->brandLogoPath,
+ ]);
+
+ $this->dispatch('notify', message: __('settings.branding_saved'));
+ }
+
+ public function removeLogo(): void
+ {
+ $c = $this->customer();
+ if ($c === null) {
+ return;
+ }
+ if ($this->brandLogoPath !== null) {
+ Storage::disk('public')->delete($this->brandLogoPath);
+ }
+ $this->brandLogoPath = null;
+ $c->update(['brand_logo_path' => null]);
+ $this->dispatch('notify', message: __('settings.branding_saved'));
+ }
+
+ private function customer(): ?Customer
+ {
+ $user = auth()->user();
+ if (! $user) {
+ return null;
+ }
+
+ return Customer::query()->where('user_id', $user->id)->first()
+ ?? Customer::query()->where('email', $user->email)->first();
+ }
+
+ #[Layout('layouts.portal-app')]
+ public function render()
+ {
+ $c = $this->customer();
+ $instance = $c?->instances()->latest('id')->first();
+
+ return view('livewire.settings', [
+ 'customer' => $c,
+ 'instance' => $instance,
+ 'branding' => $c?->brandingResolved(),
+ 'logoUrl' => $this->brandLogoPath ? Storage::disk('public')->url($this->brandLogoPath) : null,
+ 'hasActivePackage' => $instance !== null && ! in_array($instance->status, ['cancelled', 'deprovisioned'], true),
+ 'cancellationScheduled' => $instance !== null && $instance->status === 'cancellation_scheduled',
+ ]);
+ }
+}
diff --git a/app/Models/Customer.php b/app/Models/Customer.php
index 1ae7df5..b19b849 100644
--- a/app/Models/Customer.php
+++ b/app/Models/Customer.php
@@ -17,7 +17,44 @@ class Customer extends Model
/** @use HasFactory<\Database\Factories\CustomerFactory> */
use HasFactory, HasUuid;
- protected $fillable = ['user_id', 'name', 'email', 'locale', 'stripe_customer_id', 'status'];
+ protected $fillable = [
+ 'user_id', 'name', 'contact_name', 'email', 'phone', 'vat_id', 'billing_address',
+ 'locale', 'stripe_customer_id', 'status', 'closed_at',
+ 'brand_display_name', 'brand_logo_path', 'brand_primary_color', 'brand_accent_color',
+ ];
+
+ protected function casts(): array
+ {
+ return ['closed_at' => 'datetime'];
+ }
+
+ public function seats(): HasMany
+ {
+ return $this->hasMany(Seat::class);
+ }
+
+ /**
+ * Resolve branding: customer values where set, else CluPilot defaults. Used
+ * for previews and snapshotted into the provisioning run so retries are
+ * deterministic. NULL is stored for "unset" — defaults are never copied in.
+ *
+ * @return array{display_name:string,logo_path:?string,primary_color:string,accent_color:string,is_default:bool}
+ */
+ public function brandingResolved(): array
+ {
+ $defaults = (array) config('provisioning.branding_defaults');
+
+ return [
+ 'display_name' => $this->brand_display_name ?: ($defaults['display_name'] ?? 'CluPilot'),
+ 'logo_path' => $this->brand_logo_path ?: ($defaults['logo_path'] ?? null),
+ 'primary_color' => $this->brand_primary_color ?: ($defaults['primary_color'] ?? '#f97316'),
+ 'accent_color' => $this->brand_accent_color ?: ($defaults['accent_color'] ?? '#c2560a'),
+ 'is_default' => $this->brand_display_name === null
+ && $this->brand_logo_path === null
+ && $this->brand_primary_color === null
+ && $this->brand_accent_color === null,
+ ];
+ }
public function user(): BelongsTo
{
diff --git a/app/Models/Instance.php b/app/Models/Instance.php
index 1100229..6f2ac36 100644
--- a/app/Models/Instance.php
+++ b/app/Models/Instance.php
@@ -16,7 +16,7 @@ class Instance extends Model
protected $fillable = [
'customer_id', 'order_id', 'host_id', 'vmid', 'guest_ip', 'plan', 'quota_gb', 'disk_gb',
'ram_mb', 'cores', 'subdomain', 'custom_domain', 'nc_admin_ref',
- 'route_written', 'cert_ok', 'status',
+ 'route_written', 'cert_ok', 'status', 'cancel_requested_at', 'service_ends_at',
];
protected $hidden = ['nc_admin_ref'];
@@ -32,6 +32,8 @@ class Instance extends Model
'disk_gb' => 'integer',
'ram_mb' => 'integer',
'cores' => 'integer',
+ 'cancel_requested_at' => 'datetime',
+ 'service_ends_at' => 'datetime',
];
}
diff --git a/app/Models/Seat.php b/app/Models/Seat.php
new file mode 100644
index 0000000..eb3577b
--- /dev/null
+++ b/app/Models/Seat.php
@@ -0,0 +1,28 @@
+ */
+ use HasFactory, HasUuid;
+
+ public const ROLES = ['owner', 'admin', 'member', 'readonly'];
+
+ protected $fillable = ['customer_id', 'email', 'name', 'role', 'status', 'invited_at'];
+
+ protected function casts(): array
+ {
+ return ['invited_at' => 'datetime'];
+ }
+
+ public function customer(): BelongsTo
+ {
+ return $this->belongsTo(Customer::class);
+ }
+}
diff --git a/config/provisioning.php b/config/provisioning.php
index 2382c97..df1fa14 100644
--- a/config/provisioning.php
+++ b/config/provisioning.php
@@ -57,6 +57,15 @@ return [
'enterprise' => ['quota_gb' => 2000, 'disk_gb' => 2100, 'ram_mb' => 32768, 'cores' => 16, 'seats' => 500, 'performance' => 'dedicated', 'price_cents' => 79900, 'template_vmid' => 9000],
],
+ // Default branding applied when a customer has not set their own (resolved by
+ // Customer::brandingResolved(); NULL customer fields fall back to these).
+ 'branding_defaults' => [
+ 'display_name' => 'CluPilot Cloud',
+ 'logo_path' => null, // null → CluPilot logo used by the provisioner
+ 'primary_color' => '#f97316',
+ 'accent_color' => '#c2560a',
+ ],
+
// Extra storage add-on (per unit) and the add-on catalogue (labels in lang/*/billing.php).
'storage_addon' => ['gb' => 100, 'price_cents' => 1000],
'addons' => [
diff --git a/database/factories/SeatFactory.php b/database/factories/SeatFactory.php
new file mode 100644
index 0000000..744a320
--- /dev/null
+++ b/database/factories/SeatFactory.php
@@ -0,0 +1,30 @@
+ */
+class SeatFactory extends Factory
+{
+ protected $model = Seat::class;
+
+ public function definition(): array
+ {
+ return [
+ 'customer_id' => Customer::factory(),
+ 'email' => $this->faker->unique()->safeEmail(),
+ 'name' => $this->faker->name(),
+ 'role' => 'member',
+ 'status' => 'active',
+ 'invited_at' => now(),
+ ];
+ }
+
+ public function owner(): static
+ {
+ return $this->state(['role' => 'owner', 'status' => 'active']);
+ }
+}
diff --git a/database/migrations/2026_07_25_090005_add_profile_and_branding_to_customers.php b/database/migrations/2026_07_25_090005_add_profile_and_branding_to_customers.php
new file mode 100644
index 0000000..1fa506a
--- /dev/null
+++ b/database/migrations/2026_07_25_090005_add_profile_and_branding_to_customers.php
@@ -0,0 +1,39 @@
+string('contact_name')->nullable()->after('name');
+ $table->string('phone')->nullable()->after('email');
+ $table->string('vat_id')->nullable()->after('phone');
+ $table->text('billing_address')->nullable()->after('vat_id');
+
+ // Branding — NULL means "use CluPilot defaults" (resolved deterministically).
+ $table->string('brand_display_name')->nullable()->after('billing_address');
+ $table->string('brand_logo_path')->nullable()->after('brand_display_name');
+ $table->string('brand_primary_color')->nullable()->after('brand_logo_path');
+ $table->string('brand_accent_color')->nullable()->after('brand_primary_color');
+
+ // Account lifecycle: set when the customer closes their CluPilot account.
+ $table->timestamp('closed_at')->nullable()->after('status');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('customers', function (Blueprint $table) {
+ $table->dropColumn([
+ 'contact_name', 'phone', 'vat_id', 'billing_address',
+ 'brand_display_name', 'brand_logo_path', 'brand_primary_color', 'brand_accent_color',
+ 'closed_at',
+ ]);
+ });
+ }
+};
diff --git a/database/migrations/2026_07_25_090006_add_cancellation_to_instances.php b/database/migrations/2026_07_25_090006_add_cancellation_to_instances.php
new file mode 100644
index 0000000..52471fc
--- /dev/null
+++ b/database/migrations/2026_07_25_090006_add_cancellation_to_instances.php
@@ -0,0 +1,28 @@
+timestamp('cancel_requested_at')->nullable()->after('status');
+ $table->timestamp('service_ends_at')->nullable()->after('cancel_requested_at');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('instances', function (Blueprint $table) {
+ $table->dropColumn(['cancel_requested_at', 'service_ends_at']);
+ });
+ }
+};
diff --git a/database/migrations/2026_07_25_090007_create_seats_table.php b/database/migrations/2026_07_25_090007_create_seats_table.php
new file mode 100644
index 0000000..7c6bb85
--- /dev/null
+++ b/database/migrations/2026_07_25_090007_create_seats_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->uuid('uuid')->unique();
+ $table->foreignId('customer_id')->constrained()->cascadeOnDelete();
+ $table->string('email');
+ $table->string('name')->nullable();
+ $table->string('role')->default('member'); // owner | admin | member | readonly
+ $table->string('status')->default('invited'); // invited | active | revoked
+ $table->timestamp('invited_at')->nullable();
+ $table->timestamps();
+
+ $table->unique(['customer_id', 'email']);
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('seats');
+ }
+};
diff --git a/lang/de/dashboard.php b/lang/de/dashboard.php
index 07525c3..920a497 100644
--- a/lang/de/dashboard.php
+++ b/lang/de/dashboard.php
@@ -14,6 +14,7 @@ return [
'backups' => 'Backups',
'invoices' => 'Rechnungen',
'billing' => 'Paket & Addons',
+ 'settings' => 'Einstellungen',
'support' => 'Support',
],
diff --git a/lang/de/settings.php b/lang/de/settings.php
new file mode 100644
index 0000000..59d74e9
--- /dev/null
+++ b/lang/de/settings.php
@@ -0,0 +1,56 @@
+ 'Einstellungen',
+ 'subtitle' => 'Firmendaten, Branding und Ihr Paket verwalten.',
+ 'save' => 'Speichern',
+
+ 'company_title' => 'Firmendaten',
+ 'company_name' => 'Firmenname',
+ 'contact_name' => 'Ansprechpartner',
+ 'phone' => 'Telefon',
+ 'vat_id' => 'USt-IdNr.',
+ 'billing_address' => 'Rechnungsadresse',
+ 'profile_saved' => 'Firmendaten gespeichert.',
+
+ 'branding_title' => 'Branding',
+ 'branding_sub' => 'Logo und Farben werden bei der Bereitstellung auf Ihre Cloud angewendet. Ohne Angabe nutzen wir die CluPilot-Standardwerte.',
+ 'brand_display_name' => 'Anzeigename',
+ 'brand_primary' => 'Primärfarbe',
+ 'brand_accent' => 'Akzentfarbe',
+ 'brand_logo' => 'Logo',
+ 'brand_logo_hint' => 'PNG oder WebP, max. 2 MB. Transparenter Hintergrund empfohlen.',
+ 'brand_logo_remove' => 'Logo entfernen',
+ 'brand_default' => 'Standard',
+ 'brand_using_default' => 'Es werden aktuell die CluPilot-Standardwerte verwendet.',
+ 'branding_saved' => 'Branding gespeichert.',
+
+ 'package_title' => 'Paket',
+ 'package_active' => 'Aktives Paket: :plan.',
+ 'no_package' => 'Kein aktives Paket.',
+ 'cancel_cta' => 'Paket kündigen',
+ 'cancel_scheduled_title' => 'Kündigung vorgemerkt',
+ 'cancel_scheduled_body' => 'Ihr Paket endet am :date. Danach erhalten Sie Ihren Datenexport.',
+
+ 'cancel_title' => 'Paket kündigen?',
+ 'cancel_body' => 'Ihr Paket wird zum Ende der Abrechnungsperiode gekündigt.',
+ 'cancel_point_term' => 'Wirksam zum Ende der Laufzeit — bis dahin bleibt alles verfügbar.',
+ 'cancel_point_export' => 'Zum Laufzeitende erhalten Sie einen vollständigen Datenexport.',
+ 'cancel_point_irreversible' => 'Die Kündigung ist nach Bestätigung verbindlich.',
+ 'cancel_confirm_label' => 'Zum Bestätigen „:name" eingeben:',
+ 'cancel_confirm' => 'Verbindlich kündigen',
+ 'cancel_mismatch' => 'Die Eingabe stimmt nicht mit Ihrer Cloud-Adresse überein.',
+ 'keep' => 'Abbrechen',
+
+ 'close_account_title' => 'Konto schließen',
+ 'close_account_sub' => 'Ihr CluPilot-Konto dauerhaft schließen.',
+ 'close_cta' => 'Konto schließen',
+ 'close_title' => 'Konto endgültig schließen?',
+ 'close_body' => 'Ihr Zugang wird beendet. Rechnungsbelege bleiben gesetzlich aufbewahrt.',
+ 'close_confirm_label' => 'Zum Bestätigen „:word" eingeben:',
+ 'close_confirm' => 'Konto schließen',
+ 'close_keyword' => 'LÖSCHEN',
+ 'close_mismatch' => 'Bitte das Bestätigungswort korrekt eingeben.',
+ 'close_blocked_title' => 'Noch nicht möglich',
+ 'close_blocked' => 'Bitte kündigen Sie zuerst Ihr aktives Paket, bevor Sie das Konto schließen.',
+];
diff --git a/lang/en/dashboard.php b/lang/en/dashboard.php
index 242a24c..9d676f5 100644
--- a/lang/en/dashboard.php
+++ b/lang/en/dashboard.php
@@ -14,6 +14,7 @@ return [
'backups' => 'Backups',
'invoices' => 'Invoices',
'billing' => 'Plan & add-ons',
+ 'settings' => 'Settings',
'support' => 'Support',
],
diff --git a/lang/en/settings.php b/lang/en/settings.php
new file mode 100644
index 0000000..8d16e99
--- /dev/null
+++ b/lang/en/settings.php
@@ -0,0 +1,56 @@
+ 'Settings',
+ 'subtitle' => 'Manage your company details, branding and package.',
+ 'save' => 'Save',
+
+ 'company_title' => 'Company details',
+ 'company_name' => 'Company name',
+ 'contact_name' => 'Contact person',
+ 'phone' => 'Phone',
+ 'vat_id' => 'VAT ID',
+ 'billing_address' => 'Billing address',
+ 'profile_saved' => 'Company details saved.',
+
+ 'branding_title' => 'Branding',
+ 'branding_sub' => 'Your logo and colors are applied to your cloud during provisioning. If left empty we use the CluPilot defaults.',
+ 'brand_display_name' => 'Display name',
+ 'brand_primary' => 'Primary color',
+ 'brand_accent' => 'Accent color',
+ 'brand_logo' => 'Logo',
+ 'brand_logo_hint' => 'PNG or WebP, max 2 MB. Transparent background recommended.',
+ 'brand_logo_remove' => 'Remove logo',
+ 'brand_default' => 'Default',
+ 'brand_using_default' => 'Currently using the CluPilot defaults.',
+ 'branding_saved' => 'Branding saved.',
+
+ 'package_title' => 'Package',
+ 'package_active' => 'Active package: :plan.',
+ 'no_package' => 'No active package.',
+ 'cancel_cta' => 'Cancel package',
+ 'cancel_scheduled_title' => 'Cancellation scheduled',
+ 'cancel_scheduled_body' => 'Your package ends on :date. You will then receive your data export.',
+
+ 'cancel_title' => 'Cancel package?',
+ 'cancel_body' => 'Your package will be cancelled at the end of the billing period.',
+ 'cancel_point_term' => 'Effective at the end of the term — everything stays available until then.',
+ 'cancel_point_export' => 'At the end of the term you receive a full data export.',
+ 'cancel_point_irreversible' => 'Once confirmed, the cancellation is binding.',
+ 'cancel_confirm_label' => 'Type “:name” to confirm:',
+ 'cancel_confirm' => 'Cancel for good',
+ 'cancel_mismatch' => 'That does not match your cloud address.',
+ 'keep' => 'Keep',
+
+ 'close_account_title' => 'Close account',
+ 'close_account_sub' => 'Permanently close your CluPilot account.',
+ 'close_cta' => 'Close account',
+ 'close_title' => 'Close account permanently?',
+ 'close_body' => 'Your access will end. Invoices are retained as legally required.',
+ 'close_confirm_label' => 'Type “:word” to confirm:',
+ 'close_confirm' => 'Close account',
+ 'close_keyword' => 'DELETE',
+ 'close_mismatch' => 'Please type the confirmation word correctly.',
+ 'close_blocked_title' => 'Not possible yet',
+ 'close_blocked' => 'Please cancel your active package before closing the account.',
+];
diff --git a/resources/views/components/ui/icon.blade.php b/resources/views/components/ui/icon.blade.php
index db6c28a..5a942ce 100644
--- a/resources/views/components/ui/icon.blade.php
+++ b/resources/views/components/ui/icon.blade.php
@@ -27,6 +27,7 @@
'arrow-left' => '',
'rotate-ccw' => '',
'trash-2' => '',
+ 'settings' => '',
];
$body = $icons[$name] ?? '';
@endphp
diff --git a/resources/views/layouts/portal-app.blade.php b/resources/views/layouts/portal-app.blade.php
index 1d2d848..51bc39a 100644
--- a/resources/views/layouts/portal-app.blade.php
+++ b/resources/views/layouts/portal-app.blade.php
@@ -42,6 +42,7 @@
['backups', 'database', 'backups'],
['invoices', 'receipt', 'invoices'],
['billing', 'box', 'billing'],
+ ['settings', 'settings', 'settings'],
['support', 'life-buoy', 'support'],
] as [$route, $icon, $key])
@@ -102,6 +103,7 @@
+ @livewire('wire-elements-modal')
@livewireScripts