presseportale/resources/views/livewire/customer/profile.blade.php
Kevin Adametz 5b8bdf4182
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run
12-05-2026 Frontend dev
2026-05-12 18:32:33 +02:00

451 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
use App\Models\Company;
use App\Models\Profile;
use App\Models\User;
use App\Services\Image\ImageService;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Volt\Component;
use Livewire\WithFileUploads;
new #[Layout('components.layouts.app'), Title('Mein Profil')] class extends Component
{
use WithFileUploads;
public string $name = '';
public string $language = 'de';
public string $salutationKey = 'none';
public string $firstName = '';
public string $lastName = '';
public string $title = '';
public string $phone = '';
public string $address = '';
public string $countryCode = 'DE';
public string $backlinkUrl = '';
public bool $showStats = false;
public bool $disableFooterCode = false;
public string $taxIdNumber = '';
public string $billingName = '';
public string $billingAddress1 = '';
public string $billingAddress2 = '';
public string $billingPostalCode = '';
public string $billingCity = '';
public string $billingCountryCode = 'DE';
public ?int $editableCompanyId = null;
public string $companyName = '';
public string $companyAddress = '';
public string $companyEmail = '';
public string $companyPhone = '';
public string $companyWebsite = '';
public string $companyCountryCode = 'DE';
public bool $companyDisableFooterCode = false;
public $companyLogo = null;
public bool $removeCompanyLogo = false;
public function mount(): void
{
$user = auth()->user();
$profile = $user->profile;
$this->name = (string) $user->name;
$this->language = $user->language ?? 'de';
$this->salutationKey = (string) ($profile->salutation_key ?? 'none');
$this->firstName = (string) ($profile?->first_name ?? '');
$this->lastName = (string) ($profile?->last_name ?? '');
$this->title = (string) ($profile?->title ?? '');
$this->phone = (string) ($profile?->phone ?? '');
$this->address = (string) ($profile?->address ?? '');
$this->countryCode = (string) ($profile?->country_code ?? 'DE');
$this->backlinkUrl = (string) ($profile?->backlink_url ?? '');
$this->showStats = (bool) ($profile?->show_stats ?? false);
$this->disableFooterCode = (bool) ($profile?->disable_footer_code ?? false);
$this->taxIdNumber = (string) ($profile?->tax_id_number ?? '');
$billingAddress = $user->billingAddress;
$this->billingName = (string) ($billingAddress?->name ?? $user->name);
$this->billingAddress1 = (string) ($billingAddress?->address1 ?? '');
$this->billingAddress2 = (string) ($billingAddress?->address2 ?? '');
$this->billingPostalCode = (string) ($billingAddress?->postal_code ?? '');
$this->billingCity = (string) ($billingAddress?->city ?? '');
$this->billingCountryCode = (string) ($billingAddress?->country_code ?? 'DE');
$this->loadEditableCompany();
}
public function selectCompany(int $companyId): void
{
$this->editableCompanyId = $companyId;
$this->loadEditableCompany();
}
public function saveProfile(): void
{
$validated = $this->validate([
'name' => ['required', 'string', 'max:120'],
'language' => ['required', Rule::in(['de', 'en'])],
'salutationKey' => ['required', Rule::in(array_keys((array) config('salutations.items', [])))],
'firstName' => ['nullable', 'string', 'max:80'],
'lastName' => ['nullable', 'string', 'max:80'],
'title' => ['nullable', 'string', 'max:80'],
'phone' => ['nullable', 'string', 'max:40'],
'address' => ['nullable', 'string', 'max:1000'],
'countryCode' => ['nullable', 'string', 'size:2', Rule::in(array_keys((array) config('countries.items', [])))],
'backlinkUrl' => ['nullable', 'url', 'max:255'],
'taxIdNumber' => ['nullable', 'string', 'max:255'],
'billingName' => ['nullable', 'string', 'max:255'],
'billingAddress1' => ['nullable', 'string', 'max:255'],
'billingAddress2' => ['nullable', 'string', 'max:255'],
'billingPostalCode' => ['nullable', 'string', 'max:20'],
'billingCity' => ['nullable', 'string', 'max:120'],
'billingCountryCode' => ['nullable', 'string', 'size:2', Rule::in(array_keys((array) config('countries.items', [])))],
]);
if ($this->billingHasInput() && ! $this->billingIsComplete()) {
throw ValidationException::withMessages([
'billingName' => __('Bitte füllen Sie für eine Rechnungsadresse Name, Adresse, PLZ, Ort und Land aus.'),
]);
}
/** @var User $user */
$user = auth()->user();
$user->forceFill([
'name' => $validated['name'],
'language' => $validated['language'],
])->save();
$user->profile()->updateOrCreate(
['user_id' => $user->id],
[
'salutation_key' => $validated['salutationKey'],
'first_name' => $validated['firstName'] ?: null,
'last_name' => $validated['lastName'] ?: null,
'title' => $validated['title'] ?: null,
'phone' => $validated['phone'] ?: null,
'address' => $validated['address'] ?: null,
'country_code' => $validated['countryCode'] ?: null,
'backlink_url' => $validated['backlinkUrl'] ?: null,
'show_stats' => $this->showStats,
'disable_footer_code' => $this->disableFooterCode,
'tax_id_number' => $validated['taxIdNumber'] ?: null,
]
);
if (! $this->billingHasInput()) {
$user->billingAddress()->delete();
} else {
$user->billingAddress()->updateOrCreate(
['user_id' => $user->id],
[
'salutation_key' => $validated['salutationKey'] !== 'none' ? $validated['salutationKey'] : null,
'title' => $validated['title'] ?: null,
'name' => $validated['billingName'],
'address1' => $validated['billingAddress1'],
'address2' => $validated['billingAddress2'] ?: null,
'postal_code' => $validated['billingPostalCode'],
'city' => $validated['billingCity'],
'country_code' => $validated['billingCountryCode'],
],
);
}
session()->flash('profile-status', __('Profil gespeichert.'));
}
public function saveCompany(ImageService $imageService): void
{
if (! $this->editableCompanyId) {
return;
}
$company = $this->resolveEditableCompany($this->editableCompanyId);
if (! $company) {
throw ValidationException::withMessages([
'companyName' => __('Diese Firma kann von Ihnen nicht bearbeitet werden.'),
]);
}
$this->authorize('update', $company);
$validated = $this->validate([
'companyName' => ['required', 'string', 'max:255'],
'companyAddress' => ['nullable', 'string', 'max:1000'],
'companyEmail' => ['nullable', 'email', 'max:190'],
'companyPhone' => ['nullable', 'string', 'max:40'],
'companyWebsite' => ['nullable', 'url', 'max:190'],
'companyCountryCode' => ['nullable', 'string', 'size:2', Rule::in(array_keys((array) config('countries.items', [])))],
'companyLogo' => ['nullable', 'image', 'max:'.(int) (ImageService::MAX_LOGO_BYTES / 1024)],
]);
$company->fill([
'name' => $validated['companyName'],
'address' => $validated['companyAddress'] ?: null,
'email' => $validated['companyEmail'] ?: null,
'phone' => $validated['companyPhone'] ?: null,
'website' => $validated['companyWebsite'] ?: null,
'country_code' => $validated['companyCountryCode'] ?: null,
'disable_footer_code' => $this->companyDisableFooterCode,
]);
if ($this->removeCompanyLogo) {
$imageService->deleteCompanyLogo($company->logo_path, $company->logo_variants);
$company->logo_path = null;
$company->logo_variants = null;
}
if ($this->companyLogo) {
$imageService->deleteCompanyLogo($company->logo_path, $company->logo_variants);
$stored = $imageService->storeCompanyLogo(
$this->companyLogo,
$company->portal?->value ?? 'presseecho',
$company->id,
);
$company->logo_path = $stored['path'];
$company->logo_variants = $stored['variants'];
}
$company->save();
$this->companyLogo = null;
$this->removeCompanyLogo = false;
session()->flash('company-status', __('Firmendaten gespeichert.'));
}
public function with(): array
{
$user = auth()->user();
$companies = $user->companies()
->withPivot('role')
->orderBy('name')
->get(['companies.id', 'companies.name', 'companies.portal', 'companies.owner_user_id']);
return [
'user' => $user,
'companies' => $companies,
'salutations' => collect((array) config('salutations.items', []))
->map(fn (array $labels) => $labels[$user->language] ?? $labels['de'] ?? '')
->all(),
'countries' => (array) config('countries.items', []),
'editableCompany' => $this->editableCompanyId
? $this->resolveEditableCompany($this->editableCompanyId)
: null,
];
}
private function loadEditableCompany(): void
{
/** @var User $user */
$user = auth()->user();
$editable = Company::query()
->where(function ($query) use ($user): void {
$query->where('owner_user_id', $user->id)
->orWhereHas('users', fn ($q) => $q->whereKey($user->id)
->whereIn('company_user.role', ['owner', 'responsible']));
})
->orderBy('name');
$company = $this->editableCompanyId
? $editable->whereKey($this->editableCompanyId)->first()
: $editable->first();
if (! $company) {
$this->editableCompanyId = null;
return;
}
$this->editableCompanyId = $company->id;
$this->companyName = (string) $company->name;
$this->companyAddress = (string) ($company->address ?? '');
$this->companyEmail = (string) ($company->email ?? '');
$this->companyPhone = (string) ($company->phone ?? '');
$this->companyWebsite = (string) ($company->website ?? '');
$this->companyCountryCode = (string) ($company->country_code ?? 'DE');
$this->companyDisableFooterCode = (bool) $company->disable_footer_code;
}
private function resolveEditableCompany(int $companyId): ?Company
{
/** @var User $user */
$user = auth()->user();
return Company::query()
->where('id', $companyId)
->where(function ($query) use ($user): void {
$query->where('owner_user_id', $user->id)
->orWhereHas('users', fn ($q) => $q->whereKey($user->id)
->whereIn('company_user.role', ['owner', 'responsible']));
})
->first();
}
public function billingHasInput(): bool
{
return filled($this->billingName)
|| filled($this->billingAddress1)
|| filled($this->billingAddress2)
|| filled($this->billingPostalCode)
|| filled($this->billingCity);
}
public function billingIsComplete(): bool
{
return filled($this->billingName)
&& filled($this->billingAddress1)
&& filled($this->billingPostalCode)
&& filled($this->billingCity)
&& filled($this->billingCountryCode);
}
}; ?>
<div class="space-y-6">
<flux:card>
<flux:heading size="lg">{{ __('Mein Profil') }}</flux:heading>
<flux:subheading>
{{ __('Hier pflegen Sie Ihre persönlichen Konto- und Profildaten. Firmendaten verwalten Sie direkt in der jeweiligen Firma.') }}
</flux:subheading>
</flux:card>
@if(session('profile-status'))
<flux:callout color="green" icon="check-circle">{{ session('profile-status') }}</flux:callout>
@endif
<form wire:submit="saveProfile" class="grid gap-6 lg:grid-cols-2">
<flux:card>
<flux:heading size="sm" class="mb-4">{{ __('Konto') }}</flux:heading>
<div class="space-y-4">
<flux:input wire:model="name" :label="__('Name')" required />
<flux:input value="{{ $user->email }}" :label="__('E-Mail')" disabled :description="__('Änderung über Konto-Sicherheit möglich.')" />
<flux:select wire:model="language" :label="__('Sprache')">
<option value="de">Deutsch</option>
<option value="en">English</option>
</flux:select>
</div>
</flux:card>
<flux:card id="profil">
<div class="mb-4 flex flex-wrap gap-2">
<flux:badge color="indigo" size="sm">{{ __('Profil') }}</flux:badge>
<flux:badge color="zinc" size="sm">{{ __('Rechnungsadresse') }}</flux:badge>
</div>
<flux:heading size="sm" class="mb-4">{{ __('Profil') }}</flux:heading>
<div class="grid gap-4 sm:grid-cols-2">
<flux:select wire:model="salutationKey" :label="__('Anrede')">
@foreach($salutations as $key => $label)
<option value="{{ $key }}">{{ $label }}</option>
@endforeach
</flux:select>
<flux:input wire:model="title" :label="__('Titel')" placeholder="Dr." />
<flux:input wire:model="firstName" :label="__('Vorname')" />
<flux:input wire:model="lastName" :label="__('Nachname')" />
<flux:input wire:model="phone" :label="__('Telefon')" />
<flux:input wire:model="backlinkUrl" :label="__('Backlink-URL')" placeholder="https://..." />
<flux:checkbox wire:model="showStats" :label="__('Statistiken in Pressemitteilungen anzeigen')" class="sm:col-span-2" />
<flux:checkbox wire:model="disableFooterCode" :label="__('Footer-Code in Pressemitteilungen deaktivieren')" class="sm:col-span-2" />
</div>
<flux:separator class="my-6" />
<flux:heading id="rechnungsadresse" size="sm" class="mb-2">{{ __('Rechnungsadresse') }}</flux:heading>
<flux:text class="mb-4 text-sm text-zinc-500">
{{ __('Diese Angaben werden für künftige Rechnungen verwendet. Eine vollständige Rechnungsadresse benötigt Name, Adresse, PLZ, Ort und Land.') }}
</flux:text>
@if(! $this->billingIsComplete())
<flux:callout color="amber" icon="exclamation-triangle" class="mb-4">
{{ __('Rechnungsadresse noch unvollständig. Bitte ergänzen Sie die Pflichtangaben, bevor neue Buchungen sauber abgerechnet werden können.') }}
</flux:callout>
@endif
<div class="grid gap-4 sm:grid-cols-2">
<flux:input wire:model="billingName" :label="__('Rechnungsname')" class="sm:col-span-2" />
<flux:input wire:model="billingAddress1" :label="__('Adresse Zeile 1')" class="sm:col-span-2" />
<flux:input wire:model="billingAddress2" :label="__('Adresse Zeile 2')" class="sm:col-span-2" />
<flux:input wire:model="billingPostalCode" :label="__('PLZ')" />
<flux:input wire:model="billingCity" :label="__('Ort')" />
<flux:select wire:model="billingCountryCode" :label="__('Land')">
@foreach($countries as $code => $name)
<option value="{{ $code }}">{{ $name }}</option>
@endforeach
</flux:select>
<flux:input wire:model="taxIdNumber" :label="__('USt-ID')" />
<flux:error name="billingName" class="sm:col-span-2" />
</div>
</flux:card>
<div class="lg:col-span-2 flex justify-end">
<flux:button type="submit" variant="primary">{{ __('Profil speichern') }}</flux:button>
</div>
</form>
<flux:card>
<flux:heading size="sm" class="mb-4">{{ __('Zugeordnete Firmen') }}</flux:heading>
@forelse($companies as $company)
<div class="flex flex-col gap-2 border-b border-zinc-100 py-3 last:border-0 dark:border-zinc-800 sm:flex-row sm:items-center sm:justify-between">
<div class="space-y-1">
<p class="font-medium text-sm">{{ $company->name }}</p>
<div class="flex flex-wrap items-center gap-2">
<flux:badge color="zinc" size="sm">{{ $company->portal?->label() ?? '' }}</flux:badge>
<flux:badge color="indigo" size="sm">{{ $company->pivot->role ?? 'member' }}</flux:badge>
@if($company->owner_user_id === $user->id)
<flux:badge color="green" size="sm">{{ __('Eigentümer') }}</flux:badge>
@endif
</div>
</div>
@if($company->owner_user_id === $user->id || in_array($company->pivot->role, ['owner', 'responsible'], true))
<flux:button size="sm" variant="ghost" icon="arrow-right" href="{{ route('me.press-kits.show', $company->id) }}" wire:navigate>
{{ __('Firma verwalten') }}
</flux:button>
@else
<flux:button size="sm" variant="ghost" icon="arrow-right" href="{{ route('me.press-kits.show', $company->id) }}" wire:navigate>
{{ __('Firma öffnen') }}
</flux:button>
@endif
</div>
@empty
<flux:text class="text-sm text-zinc-500">
{{ __('Keine Firmen zugeordnet. Bitte wenden Sie sich an den Administrator.') }}
</flux:text>
@endforelse
</flux:card>
</div>