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->requireCustomer(); 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->requireCustomer(); 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', ]); // Store the new upload, but keep the old file until the DB row that // references it is updated — delete the old one only after that commits, // so a failed update never orphans a file or dangles a reference. $oldToDelete = null; if ($this->logo !== null) { $oldToDelete = $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, ]); if ($oldToDelete !== null && $oldToDelete !== $this->brandLogoPath) { Storage::disk('public')->delete($oldToDelete); } $this->dispatch('notify', message: __('settings.branding_saved')); } public function removeLogo(): void { $c = $this->requireCustomer(); 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')); } #[Layout('layouts.portal-app')] public function render() { $c = $this->customer(); // Prefer the active/cancelling instance for the package section so the // controls line up with what cancellation actually targets. $active = $c?->instances()->where('status', 'active')->latest('id')->first(); $scheduled = $c?->instances()->where('status', 'cancellation_scheduled')->latest('id')->first(); $instance = $active ?? $scheduled ?? $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' => $active !== null, 'cancellationScheduled' => $active === null && $scheduled !== null, ]); } }