implement uploading file

This commit is contained in:
2026-05-01 02:02:34 +03:30
parent 4776af5c2a
commit 12b6bb4d76
11 changed files with 589 additions and 24 deletions

View File

@@ -52,7 +52,24 @@
</flux:tooltip>
<flux:tooltip :content="__('Files')" position="top">
<flux:button type="button" variant="filled" icon="folder" aria-label="{{ __('Files') }}" />
<div class="relative">
<flux:button
type="button"
variant="filled"
icon="folder"
wire:click="openFiles"
wire:loading.attr="disabled"
wire:target="openFiles"
aria-label="{{ __('Files') }}"
class="w-full"
/>
@if ((int) $this->conversation->files_count > 0)
<span class="absolute -end-1 -top-1 flex min-w-5 items-center justify-center rounded-full bg-zinc-900 px-1 text-[10px] font-semibold text-white shadow-sm dark:bg-white dark:text-zinc-950">
{{ $this->conversation->files_count }}
</span>
@endif
</div>
</flux:tooltip>
</div>
</section>
@@ -120,6 +137,11 @@
<div class="text-xs text-zinc-500 dark:text-zinc-400">{{ __('Messages') }}</div>
<div class="mt-1 font-semibold text-zinc-900 dark:text-zinc-100">{{ $this->conversation->messages_count }}</div>
</div>
<div class="rounded-lg border border-zinc-200 bg-white p-3 dark:border-zinc-800 dark:bg-zinc-950">
<div class="text-xs text-zinc-500 dark:text-zinc-400">{{ __('Files') }}</div>
<div class="mt-1 font-semibold text-zinc-900 dark:text-zinc-100">{{ $this->conversation->files_count }}</div>
</div>
</div>
</section>
</div>
@@ -235,4 +257,70 @@
</div>
</form>
</flux:modal>
<flux:modal wire:model="showFilesModal" class="w-full max-w-2xl">
<div class="space-y-6">
<div class="flex items-start justify-between gap-4">
<div>
<flux:heading size="lg">{{ __('Conversation files') }}</flux:heading>
<flux:text class="mt-1 text-sm text-zinc-500 dark:text-zinc-400">
{{ __('Recent files shared in this conversation.') }}
</flux:text>
</div>
<flux:badge color="zinc">
{{ trans_choice(':count file|:count files', $this->conversation->files_count, ['count' => $this->conversation->files_count]) }}
</flux:badge>
</div>
<div class="max-h-[30rem] overflow-y-auto rounded-lg border border-zinc-200 bg-zinc-50 p-2 dark:border-zinc-800 dark:bg-zinc-950">
@forelse ($this->files as $file)
<a
wire:key="details-file-{{ $file->id }}"
href="{{ route('messages.attachment.download', $file) }}"
class="group flex items-center gap-3 rounded-md p-3 text-left transition hover:bg-white focus:outline-none focus-visible:ring-2 focus-visible:ring-accent dark:hover:bg-zinc-900"
>
<span class="flex size-11 shrink-0 items-center justify-center rounded-lg bg-white text-zinc-500 shadow-sm ring-1 ring-zinc-200 dark:bg-zinc-900 dark:text-zinc-300 dark:ring-zinc-800">
<flux:icon.document class="size-5" />
</span>
<span class="min-w-0 flex-1">
<span class="block truncate text-sm font-medium text-zinc-900 dark:text-zinc-100">
{{ $file->attachmentName() }}
</span>
<span class="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-zinc-500 dark:text-zinc-400">
<span>{{ $file->formattedAttachmentSize() }}</span>
<span aria-hidden="true">&middot;</span>
<span>{{ $file->sender?->name ?? __('Deleted user') }}</span>
<span aria-hidden="true">&middot;</span>
<span>{{ $file->created_at->format('M j, H:i') }}</span>
</span>
</span>
<span class="flex size-9 shrink-0 items-center justify-center rounded-full text-zinc-400 transition group-hover:bg-zinc-100 group-hover:text-zinc-700 dark:text-zinc-500 dark:group-hover:bg-zinc-800 dark:group-hover:text-zinc-200">
<flux:icon.arrow-down-tray class="size-4" />
</span>
</a>
@empty
<div class="flex min-h-56 items-center justify-center p-8 text-center">
<div class="max-w-xs">
<div class="mx-auto mb-4 flex size-12 items-center justify-center rounded-lg border border-dashed border-zinc-300 text-zinc-400 dark:border-zinc-700">
<flux:icon.folder-open class="size-6" />
</div>
<flux:heading size="sm">{{ __('No files shared yet') }}</flux:heading>
<flux:text class="mt-2 text-sm text-zinc-500 dark:text-zinc-400">
{{ __('Files you send in the composer will appear here for quick access.') }}
</flux:text>
</div>
</div>
@endforelse
</div>
<div class="flex justify-end border-t border-zinc-200 pt-4 dark:border-zinc-800">
<flux:modal.close>
<flux:button type="button" variant="ghost">{{ __('Close') }}</flux:button>
</flux:modal.close>
</div>
</div>
</flux:modal>
</aside>

View File

@@ -97,7 +97,43 @@
'bg-zinc-900 text-white dark:bg-white dark:text-zinc-950' => $isMine,
'border border-zinc-200 bg-white text-zinc-800 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100' => ! $isMine,
])>
<p class="whitespace-pre-wrap break-words">{{ $message->body }}</p>
@if ($message->isFile())
<a
href="{{ route('messages.attachment.download', $message) }}"
@class([
'group flex min-w-0 items-center gap-3 rounded-md border p-3 text-left transition',
'border-white/15 bg-white/10 hover:bg-white/15 focus:outline-none focus-visible:ring-2 focus-visible:ring-white/60 dark:border-zinc-950/10 dark:bg-zinc-950/5 dark:hover:bg-zinc-950/10 dark:focus-visible:ring-zinc-950/40' => $isMine,
'border-zinc-200 bg-zinc-50 hover:bg-zinc-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent dark:border-zinc-700 dark:bg-zinc-950 dark:hover:bg-zinc-800' => ! $isMine,
])
>
<span @class([
'flex size-10 shrink-0 items-center justify-center rounded-md',
'bg-white/15 text-white dark:bg-zinc-950/10 dark:text-zinc-800' => $isMine,
'bg-white text-zinc-500 shadow-sm dark:bg-zinc-900 dark:text-zinc-300' => ! $isMine,
])>
<flux:icon.document class="size-5" />
</span>
<span class="min-w-0 flex-1">
<span class="block truncate font-medium">{{ $message->attachmentName() }}</span>
<span @class([
'mt-0.5 block text-xs',
'text-white/70 dark:text-zinc-600' => $isMine,
'text-zinc-500 dark:text-zinc-400' => ! $isMine,
])>
{{ $message->formattedAttachmentSize() }}
</span>
</span>
<flux:icon.arrow-down-tray @class([
'size-4 shrink-0 transition group-hover:translate-y-0.5',
'text-white/70 dark:text-zinc-600' => $isMine,
'text-zinc-400 dark:text-zinc-500' => ! $isMine,
]) />
</a>
@else
<p class="whitespace-pre-wrap break-words">{{ $message->body }}</p>
@endif
</div>
<div @class([

View File

@@ -1,7 +1,93 @@
<form wire:submit="sendMessage" class="shrink-0 border-t border-zinc-200 bg-white/90 p-3 backdrop-blur dark:border-zinc-800 dark:bg-zinc-950/90 sm:p-4">
<form
wire:submit="sendMessage"
x-data="{ attachmentsOpen: {{ count($attachments) > 0 ? 'true' : 'false' }} }"
class="shrink-0 border-t border-zinc-200 bg-white/90 p-3 backdrop-blur dark:border-zinc-800 dark:bg-zinc-950/90 sm:p-4"
>
<input
x-ref="fileInput"
type="file"
wire:model="attachments"
x-on:change="attachmentsOpen = true"
multiple
class="hidden"
>
<div
x-cloak
x-show="attachmentsOpen || {{ count($attachments) > 0 ? 'true' : 'false' }}"
x-transition.opacity.duration.150ms
class="mb-2 rounded-lg border border-zinc-200 bg-white p-3 shadow-sm dark:border-zinc-800 dark:bg-zinc-900"
>
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-zinc-900 dark:text-zinc-100">{{ __('Attachments') }}</div>
<div class="mt-0.5 text-xs text-zinc-500 dark:text-zinc-400">{{ __('Up to 5 files, 10 MB each') }}</div>
</div>
<flux:button
type="button"
size="sm"
variant="ghost"
icon="plus"
x-on:click="$refs.fileInput.click()"
>
{{ __('Add files') }}
</flux:button>
</div>
<div wire:loading.flex wire:target="attachments" class="mt-3 items-center gap-3 rounded-md border border-dashed border-zinc-300 bg-zinc-50 p-3 text-sm text-zinc-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-400">
<flux:icon.arrow-path class="size-4 animate-spin" />
<span>{{ __('Uploading files...') }}</span>
</div>
@if (count($attachments) > 0)
<div class="mt-3 grid gap-2">
@foreach ($attachments as $index => $attachment)
<div wire:key="composer-attachment-{{ $index }}" class="flex items-center gap-3 rounded-md border border-zinc-200 bg-zinc-50 p-2 dark:border-zinc-800 dark:bg-zinc-950">
<div class="flex size-9 shrink-0 items-center justify-center rounded-md bg-white text-zinc-500 shadow-sm dark:bg-zinc-900 dark:text-zinc-300">
<flux:icon.document class="size-5" />
</div>
<div class="min-w-0 flex-1">
<div class="truncate text-sm font-medium text-zinc-900 dark:text-zinc-100">
{{ $attachment->getClientOriginalName() }}
</div>
<div class="text-xs text-zinc-500 dark:text-zinc-400">
{{ \Illuminate\Support\Number::fileSize((int) $attachment->getSize()) }}
</div>
</div>
<flux:button
type="button"
size="sm"
variant="ghost"
icon="x-mark"
wire:click="removeAttachment({{ $index }})"
aria-label="{{ __('Remove attachment') }}"
/>
</div>
@endforeach
</div>
@endif
@error('attachments')
<flux:text class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</flux:text>
@enderror
@error('attachments.*')
<flux:text class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</flux:text>
@enderror
</div>
<div class="flex items-end gap-2 rounded-lg border border-zinc-200 bg-white p-2 shadow-sm transition focus-within:border-zinc-300 focus-within:ring-2 focus-within:ring-accent focus-within:ring-offset-2 focus-within:ring-offset-white dark:border-zinc-800 dark:bg-zinc-900 dark:focus-within:border-zinc-700 dark:focus-within:ring-offset-zinc-950">
<flux:tooltip :content="__('Attach file')" position="top">
<flux:button type="button" variant="ghost" icon="paper-clip" aria-label="{{ __('Attach file') }}" />
<flux:button
type="button"
variant="ghost"
icon="paper-clip"
x-on:click="$refs.fileInput.click()"
aria-label="{{ __('Attach file') }}"
/>
</flux:tooltip>
<flux:textarea
@@ -22,9 +108,9 @@
type="submit"
variant="primary"
icon="paper-airplane"
:disabled="blank(trim($body))"
:disabled="blank(trim($body)) && count($attachments) === 0"
wire:loading.attr="disabled"
wire:target="sendMessage"
wire:target="attachments,sendMessage"
aria-label="{{ __('Send message') }}"
/>
</div>