171 lines
5.4 KiB
PHP
171 lines
5.4 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire;
|
|
|
|
use App\Models\Customer;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Attributes\Validate;
|
|
use Livewire\Component;
|
|
use Livewire\WithFileUploads;
|
|
|
|
class Settings extends Component
|
|
{
|
|
use WithFileUploads;
|
|
|
|
// Company / billing profile
|
|
#[Validate('required|string|max:255')]
|
|
public string $companyName = '';
|
|
|
|
#[Validate('nullable|string|max:255')]
|
|
public string $contactName = '';
|
|
|
|
#[Validate('nullable|string|max:64')]
|
|
public string $phone = '';
|
|
|
|
#[Validate('nullable|string|max:64')]
|
|
public string $vatId = '';
|
|
|
|
#[Validate('nullable|string|max:2000')]
|
|
public string $billingAddress = '';
|
|
|
|
// Branding
|
|
#[Validate('nullable|string|max:255')]
|
|
public string $brandDisplayName = '';
|
|
|
|
#[Validate('nullable|regex:/^#[0-9a-fA-F]{6}$/')]
|
|
public string $brandPrimary = '';
|
|
|
|
#[Validate('nullable|regex:/^#[0-9a-fA-F]{6}$/')]
|
|
public string $brandAccent = '';
|
|
|
|
/** New logo upload (validated on save). */
|
|
public $logo = null;
|
|
|
|
public ?string $brandLogoPath = null;
|
|
|
|
public function mount(): void
|
|
{
|
|
$c = $this->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) {
|
|
// Store the new file FIRST; only drop the old one once the write
|
|
// succeeded, so a failed upload never leaves a dangling reference.
|
|
$old = $this->brandLogoPath;
|
|
$this->brandLogoPath = $this->logo->store('branding', 'public');
|
|
if ($old !== null && $old !== $this->brandLogoPath) {
|
|
Storage::disk('public')->delete($old);
|
|
}
|
|
$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();
|
|
// 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,
|
|
]);
|
|
}
|
|
}
|