CluPilotCloud/resources/views/livewire/admin/mail.blade.php

257 lines
16 KiB
PHP

<div class="mx-auto max-w-[1120px] space-y-6">
<div class="animate-rise">
<h1 class="text-2xl font-bold tracking-tight text-ink">{{ __('mail_settings.title') }}</h1>
<p class="mt-1 max-w-[70ch] text-sm text-muted">{{ __('mail_settings.subtitle') }}</p>
</div>
{{-- ── Die Reiter ────────────────────────────────────────────────────
Das waren vier Karten untereinander in einer schmalen Spalte, und die
letzten beiden allein zweiundzwanzig gleich aussehende Zeilen mit zwei
gleich aussehenden Speichern-Knöpfen darunter. Geteilt nach der Frage,
die man an die Seite mitbringt: WOMIT wird gesendet, WER sendet, und
WAS geht von wo raus dieselbe Achse, nach der Admin\Integrations aus
demselben Grund umgebaut wurde.
Der offene Reiter steht in der Adresszeile, damit ein Neuladen oder
ein Lesezeichen dort landet, wo der Betreiber war. Die Bestätigung des
Passworts überlebt einen Wechsel: sie gehört zur Sitzung, nicht zum
Reiter. --}}
<div class="flex flex-wrap items-center gap-x-1 gap-y-2 border-b border-line animate-rise [animation-delay:40ms]" role="tablist">
@foreach (\App\Livewire\Admin\Mail::TABS as $name)
<button type="button" role="tab" wire:click="$set('tab', '{{ $name }}')"
aria-selected="{{ $tab === $name ? 'true' : 'false' }}"
@class([
'flex items-center gap-2 whitespace-nowrap border-b-2 px-4 py-2.5 text-sm font-medium transition-colors -mb-px',
'border-accent-active text-ink' => $tab === $name,
'border-transparent text-muted hover:text-ink' => $tab !== $name,
])>
<x-ui.icon :name="['versand' => 'server', 'postfaecher' => 'mail', 'zuordnung' => 'send'][$name] ?? 'settings'"
class="size-4" />{{ __('mail_settings.tab.'.$name) }}
</button>
@endforeach
</div>
{{-- Beide Hinweise stehen ÜBER den Reitern, nicht in einem davon: sie
gelten für die ganze Seite. Der erste ist das eine, was jedes Postfach
hier zur Zierde macht und er gehört genau dorthin, wo jemand
nachsieht, warum keine Mail ankam; der Postfach-Test darunter meldet
nämlich trotzdem Erfolg, weil MailboxTester absichtlich seinen eigenen
Weg baut. --}}
@if (! \App\Support\MailDelivery::delivers())
<x-ui.alert variant="warning">
{{ __('admin.notice.mail_not_delivering', ['mailer' => (string) (\App\Support\MailDelivery::transport() ?? 'null')]) }}
</x-ui.alert>
@endif
@if (! $usable)
<x-ui.alert variant="warning">{{ __('mail_settings.no_key') }}</x-ui.alert>
@endif
{{-- ── 1. Womit gesendet wird ───────────────────────────────────────
Ein Server, eine Karte. Der Hauptschalter steht ÜBER den Serverfeldern
und in derselben Karte: er entscheidet, ob die überhaupt benutzt
werden, und derselbe Knopf speichert beides. --}}
@if ($tab === 'versand')
<x-ui.card class="animate-rise [animation-delay:80ms]">
<h2 class="text-lg font-semibold text-ink">{{ __('mail_settings.server_title') }}</h2>
<p class="mt-1 max-w-[70ch] text-sm text-muted">{{ __('mail_settings.server_hint') }}</p>
<div class="mt-5 rounded-lg border border-line bg-surface-2 px-4 py-3.5">
<x-ui.switch name="deliver" wire:model.live="deliver"
:label="__('mail_settings.deliver')"
:hint="__('mail_settings.deliver_hint')"
:on="__('mail_settings.deliver_on')"
:off="__('mail_settings.deliver_off')" />
</div>
<div class="mt-5 grid gap-4 sm:grid-cols-3">
<x-ui.input name="host" wire:model="host" :label="__('mail_settings.host')" />
<x-ui.input name="port" type="number" wire:model="port" :label="__('mail_settings.port')" />
<div>
<label for="encryption" class="text-sm font-medium text-ink">{{ __('mail_settings.encryption') }}</label>
<select id="encryption" wire:model="encryption"
class="mt-1.5 w-full rounded-md border border-line-strong bg-surface px-3 py-2 text-sm text-body">
<option value="tls">TLS</option>
<option value="ssl">SSL</option>
<option value="none">{{ __('mail_settings.encryption_none') }}</option>
</select>
@error('encryption')<p class="mt-1 text-xs text-danger">{{ $message }}</p>@enderror
</div>
</div>
@if (! $passwordConfirmed)
{{-- The second gate. $host is the platform's outbound relay for
every purpose at once — being signed in is not enough to
repoint it, the same reasoning as the credentials page. --}}
<form wire:submit="confirmPassword" class="mt-5 rounded-lg border border-line bg-surface-2 p-4">
<p class="text-sm text-body">{{ __('mail_settings.confirm_first') }}</p>
<div class="mt-3 flex flex-wrap items-start gap-2">
<div class="min-w-56 flex-1">
<x-ui.input name="confirmablePassword" type="password" autocomplete="current-password"
:label="__('admin_settings.password_current')" wire:model="confirmablePassword" />
</div>
<x-ui.button type="submit" variant="secondary" class="mt-7" wire:loading.attr="disabled" wire:target="confirmPassword">
{{ __('mail_settings.confirm_button') }}
</x-ui.button>
</div>
</form>
@else
<x-ui.button wire:click="saveServer" variant="primary" size="md" class="mt-5">
{{ __('mail_settings.save') }}
</x-ui.button>
@endif
</x-ui.card>
@endif
{{-- ── 2. Wer sendet ────────────────────────────────────────────────
Kein Eingabefeld in einem <td> — R20. Das Empfängerfeld für den
Testversand steht ÜBER der Tabelle, eines für alle Zeilen. --}}
@if ($tab === 'postfaecher')
<x-ui.card class="animate-rise [animation-delay:80ms]">
<h2 class="text-lg font-semibold text-ink">{{ __('mail_settings.boxes_title') }}</h2>
<div class="mt-4 max-w-sm">
<x-ui.input name="testRecipient" type="email" wire:model="testRecipient" :label="__('mail_settings.test_recipient')" />
<p class="mt-2 text-sm text-muted">{{ __('mail_settings.test_hint') }}</p>
</div>
@if ($testResult)
<x-ui.alert :variant="$testResult['ok'] ? 'success' : 'danger'" class="mt-4">
<span class="font-medium">{{ $testedKey }}:</span>
@if ($testResult['ok'])
{{ __('mail_settings.test_ok') }}
@else
{{ __('mail_settings.test_failed') }} {{ $testResult['error'] }}
@endif
</x-ui.alert>
@endif
<table class="mt-5 w-full text-sm">
<thead>
<tr class="border-b border-line text-left">
<th class="lbl pb-2">{{ __('mail_settings.address') }}</th>
<th class="lbl pb-2">{{ __('mail_settings.last_verified') }}</th>
<th class="lbl pb-2"></th>
</tr>
</thead>
<tbody>
@foreach ($mailboxes as $box)
<tr wire:key="mailbox-{{ $box->uuid }}" class="border-b border-line last:border-0">
<td class="py-3">
<span class="font-medium text-ink">{{ $box->address }}</span>
<span class="ml-2 text-muted">{{ $box->key }}</span>
</td>
<td class="py-3 text-muted">
{{ $box->last_verified_at?->local()->isoFormat('DD.MM.YYYY HH:mm') ?? __('mail_settings.never_verified') }}
</td>
<td class="py-3 text-right space-x-2">
{{-- R18: icon beside its text, single line, size-4.
Two spans swapped by wire:loading, same
pattern as admin/host-create.blade.php's save
button the disabled attribute alone left an
operator staring at a greyed-out button with
no sign anything was happening for up to the
full connect timeout. --}}
<x-ui.button size="sm" variant="ghost" wire:click="test('{{ $box->uuid }}')"
wire:loading.attr="disabled" wire:target="test('{{ $box->uuid }}')">
<x-ui.icon name="send" class="size-4" />
<span wire:loading.remove wire:target="test('{{ $box->uuid }}')">{{ __('mail_settings.test') }}</span>
<span wire:loading wire:target="test('{{ $box->uuid }}')">{{ __('mail_settings.testing') }}</span>
</x-ui.button>
<x-ui.button size="sm" variant="ghost"
x-on:click="$dispatch('openModal', { component: 'edit-mailbox', arguments: { uuid: '{{ $box->uuid }}' } })">
<x-ui.icon name="pencil" class="size-4" />{{ __('mail_settings.edit') }}
</x-ui.button>
</td>
</tr>
@endforeach
</tbody>
</table>
</x-ui.card>
@endif
{{-- ── 3. Was von wo rausgeht ───────────────────────────────────────
Zwei Listen waren es: fünf Zwecke, darunter siebzehn Mailarten, beide
als „Beschriftung, Auswahlfeld" und optisch nicht auseinanderzuhalten.
Dass die zweite Liste auf die erste zurückfällt, stand nur als Satz in
der leeren Auswahl — siebzehnmal derselbe Satz.
Jetzt steht jede Mailart unter ihrem Zweck: oben das Postfach, aus dem
der Zweck verschickt, darunter eingerückt die Mailarten, die davon
abweichen dürfen. Die Regel ist damit die Form, und die leere Auswahl
nennt die Adresse, die stattdessen greift, statt nur den Zwecknamen.
Ein <select> je Zeile ist R20's ausdrückliche Ausnahme: ein Klick, ein
Wert, keine Höhenänderung. --}}
@if ($tab === 'zuordnung')
<x-ui.card class="animate-rise [animation-delay:80ms]">
<h2 class="text-lg font-semibold text-ink">{{ __('mail_settings.purposes_title') }}</h2>
<p class="mt-1 max-w-[70ch] text-sm text-muted">{{ __('mail_settings.assignments_hint') }}</p>
<div class="mt-6 space-y-5">
@foreach ($catalogueByPurpose as $purpose => $entries)
@php
// Die Adresse, die WIRKLICH greift, wenn eine Mailart
// nichts Eigenes hat — vom Bauteil über dieselbe
// Auflösung geholt, die beim Senden entscheidet. Das
// hier zugeordnete Postfach ist nicht dieselbe Antwort:
// ein abgeschaltetes fällt auf „System" zurück.
$inherited = $inheritedAddress[$purpose] ?? null;
@endphp
<section wire:key="purpose-{{ $purpose }}" class="rounded-lg border border-line">
<div class="flex flex-wrap items-center justify-between gap-x-6 gap-y-3 rounded-t-lg bg-surface-2 px-4 py-3">
<div>
<h3 class="text-sm font-semibold text-ink">{{ __('mail_settings.purpose.'.$purpose) }}</h3>
<p class="mt-0.5 text-xs text-muted">{{ __('mail_settings.purpose_mailbox') }}</p>
</div>
<select wire:model="purposes.{{ $purpose }}"
aria-label="{{ __('mail_settings.purpose_mailbox') }}"
class="w-full rounded-md border border-line-strong bg-surface px-3 py-2 text-sm text-body sm:w-72">
<option value=""></option>
@foreach ($mailboxes as $box)
<option value="{{ $box->key }}">{{ $box->address }}</option>
@endforeach
</select>
</div>
@error('purposes.'.$purpose)
<p class="border-t border-line px-4 py-2 text-xs text-danger">{{ $message }}</p>
@enderror
@if ($entries === [])
<p class="px-4 py-3 text-sm text-muted">{{ __('mail_settings.purpose_no_types') }}</p>
@else
<p class="lbl border-t border-line px-4 pt-3">{{ __('mail_settings.route_exceptions') }}</p>
<div class="divide-y divide-line px-4 pb-2">
@foreach ($entries as $key => $entry)
<div wire:key="route-{{ $key }}"
class="flex flex-wrap items-center justify-between gap-x-6 gap-y-2 py-2.5">
<span class="text-sm text-body">{{ $entry['label'] }}</span>
<select wire:model="routes.{{ $key }}"
aria-label="{{ $entry['label'] }}"
class="w-full rounded-md border border-line-strong bg-surface px-3 py-2 text-sm text-body sm:w-72">
<option value="">
{{ $inherited === null
? __('mail_settings.route_inherit_unset')
: __('mail_settings.route_inherit', ['address' => $inherited]) }}
</option>
@foreach ($mailboxes->where('active', true) as $box)
<option value="{{ $box->key }}">{{ $box->address }}</option>
@endforeach
</select>
</div>
@endforeach
</div>
@endif
</section>
@endforeach
</div>
<x-ui.button wire:click="saveAssignments" variant="primary" size="md" class="mt-6">
{{ __('mail_settings.save') }}
</x-ui.button>
</x-ui.card>
@endif
</div>