141 lines
7.8 KiB
PHP
141 lines
7.8 KiB
PHP
<div class="mx-auto max-w-[1120px] space-y-5">
|
|
<div class="flex flex-wrap items-start gap-4 animate-rise">
|
|
<div class="min-w-0">
|
|
<h1 class="text-2xl font-bold tracking-tight text-ink">{{ __('inbox.title') }}</h1>
|
|
<p class="mt-1 max-w-[70ch] text-sm text-muted">{{ __('inbox.subtitle') }}</p>
|
|
</div>
|
|
<div class="ml-auto flex flex-wrap items-center gap-2">
|
|
<x-ui.button wire:click="fetchNow" variant="secondary" size="sm"
|
|
wire:loading.attr="disabled" wire:target="fetchNow">
|
|
<x-ui.icon name="refresh" class="size-4" />{{ __('inbox.fetch_now') }}
|
|
</x-ui.button>
|
|
<x-ui.button wire:click="$toggle('archived')" :variant="$archived ? 'primary' : 'secondary'" size="sm">
|
|
{{ $archived ? __('inbox.show_open') : __('inbox.show_archived') }}
|
|
</x-ui.button>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- A mailbox nobody configured looks exactly like a mailbox with no mail
|
|
in it. Said out loud, with what is missing. --}}
|
|
@if (! $configured)
|
|
<x-ui.alert variant="warning">
|
|
{{ __('inbox.not_configured') }}
|
|
<a href="{{ route('admin.integrations') }}" class="font-semibold underline">{{ __('inbox.to_integrations') }}</a>
|
|
</x-ui.alert>
|
|
@endif
|
|
|
|
{{-- When the mailbox was last reached. Shown whether or not anything came of
|
|
it: an empty inbox and a mailbox that stopped answering look exactly the
|
|
same, and the difference is this line. --}}
|
|
@if ($configured)
|
|
<p @class([
|
|
'flex flex-wrap items-center gap-1.5 text-xs',
|
|
'text-muted' => $status === null || $status['ok'],
|
|
'text-danger' => $status !== null && ! $status['ok'],
|
|
])>
|
|
@if ($status === null)
|
|
{{ __('inbox.never_checked') }}
|
|
@else
|
|
<x-ui.icon :name="$status['ok'] ? 'check' : 'alert-triangle'" class="size-3.5" />
|
|
{{-- R19: stored in UTC, read on the wall clock. --}}
|
|
{{ __($status['ok'] ? 'inbox.last_ok' : 'inbox.last_failed', [
|
|
'when' => $status['at']->local()->isoFormat('D. MMM YYYY, HH:mm'),
|
|
'ago' => $status['at']->diffForHumans(),
|
|
]) }}
|
|
@if (! $status['ok'])
|
|
<a href="{{ route('admin.integrations') }}" class="font-semibold underline">{{ __('inbox.to_integrations') }}</a>
|
|
@endif
|
|
@endif
|
|
</p>
|
|
@endif
|
|
|
|
@if ($mails->isEmpty())
|
|
<div class="rounded-lg border border-line bg-surface p-10 text-center shadow-xs animate-rise">
|
|
<p class="text-sm text-muted">{{ $archived ? __('inbox.empty_archived') : __('inbox.empty') }}</p>
|
|
</div>
|
|
@else
|
|
<div class="space-y-4">
|
|
@foreach ($mails as $mail)
|
|
<div wire:key="mail-{{ $mail->uuid }}"
|
|
@class([
|
|
'rounded-lg border bg-surface p-6 shadow-xs animate-rise',
|
|
// The unplaced one is marked, because it is the one
|
|
// that gets forgotten.
|
|
'border-warning-border' => $mail->isUnassigned(),
|
|
'border-line' => ! $mail->isUnassigned(),
|
|
])>
|
|
<div class="flex flex-wrap items-baseline gap-x-3 gap-y-1">
|
|
<span class="font-semibold text-ink">{{ $mail->subject ?: __('inbox.no_subject') }}</span>
|
|
@if ($mail->isUnassigned())
|
|
<x-ui.badge status="warning">{{ __('inbox.unassigned') }}</x-ui.badge>
|
|
@endif
|
|
{{-- R19: stored in UTC, read on the wall clock. --}}
|
|
<span class="ml-auto font-mono text-xs text-muted">
|
|
{{ $mail->received_at->local()->isoFormat('D. MMM YYYY, HH:mm') }}
|
|
</span>
|
|
</div>
|
|
|
|
<p class="mt-1 font-mono text-xs text-muted">
|
|
{{ $mail->from_name ? $mail->from_name.' · ' : '' }}{{ $mail->from_email }}
|
|
</p>
|
|
|
|
{{-- Their own words. Escaped by Blade, printed as text: this
|
|
came from a stranger and is never rendered as markup. --}}
|
|
<p class="mt-3 whitespace-pre-line text-sm leading-relaxed text-body">{{ $mail->body }}</p>
|
|
|
|
@if ($mail->hasAttachments())
|
|
{{-- Names and sizes. The files stay on the mail server —
|
|
keeping whatever a stranger attaches would make this
|
|
a malware store with a console in front of it. --}}
|
|
<div class="mt-4 rounded-md border border-line bg-surface-2 p-3">
|
|
<p class="lbl">{{ __('inbox.attachments') }}</p>
|
|
<ul class="mt-2 space-y-1">
|
|
@foreach ($mail->attachments as $file)
|
|
<li class="flex items-center gap-2 font-mono text-xs text-muted">
|
|
<x-ui.icon name="file-text" class="size-3.5 shrink-0" />
|
|
{{ $file['name'] }}
|
|
<span class="text-faint">{{ number_format(($file['bytes'] ?? 0) / 1024, 0, ',', '.') }} KB</span>
|
|
</li>
|
|
@endforeach
|
|
</ul>
|
|
<p class="mt-2 text-xs text-muted">{{ __('inbox.attachments_note') }}</p>
|
|
</div>
|
|
@endif
|
|
|
|
<div class="mt-4 flex flex-wrap items-center gap-2 border-t border-line pt-4">
|
|
@if ($mail->customer)
|
|
{{-- Straight to the page with the answer box on it. --}}
|
|
<x-ui.button :href="route('admin.customer', $mail->customer->uuid)" variant="primary" size="sm" wire:navigate>
|
|
<x-ui.icon name="send" class="size-4" />{{ __('inbox.answer', ['name' => $mail->customer->name]) }}
|
|
</x-ui.button>
|
|
@else
|
|
{{-- One select, one value, no height change — R20's
|
|
exception. The address decides automatically;
|
|
this is for the customer writing from their
|
|
private account, whom no matcher will ever
|
|
recognise and an operator recognises at once. --}}
|
|
<select wire:change="assign('{{ $mail->uuid }}', $event.target.value)"
|
|
class="rounded-md border border-line-strong bg-surface px-2 py-1.5 text-xs text-body">
|
|
<option value="">{{ __('inbox.assign_to') }}</option>
|
|
@foreach ($customers as $c)
|
|
<option value="{{ $c->uuid }}">{{ $c->name }} · {{ $c->email }}</option>
|
|
@endforeach
|
|
</select>
|
|
@endif
|
|
|
|
@if ($mail->archived_at)
|
|
<x-ui.button wire:click="unarchive('{{ $mail->uuid }}')" variant="ghost" size="sm" class="ml-auto">
|
|
{{ __('inbox.unarchive') }}
|
|
</x-ui.button>
|
|
@else
|
|
<x-ui.button wire:click="archive('{{ $mail->uuid }}')" variant="ghost" size="sm" class="ml-auto">
|
|
{{ __('inbox.archive') }}
|
|
</x-ui.button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</div>
|