Improve live recordings UI, upload flow, and default Flux theme.

Refresh list status over Reverb with polling fallback, clarify queued vs processing, streamline upload empty states, and seed an admin login.
This commit is contained in:
ben
2026-08-12 20:14:35 +02:00
parent 148ba91816
commit c17f8fb506
28 changed files with 523 additions and 333 deletions
@@ -1,25 +1,8 @@
<div
x-data="recordingsIndex(@js([
'recordings' => $recordings->map(fn ($recording) => [
'id' => $recording->id,
'status' => $recording->transcription_status,
'status_label' => $recording->transcriptionStatusLabel(),
'progress' => $recording->transcription_progress,
'percent' => $recording->transcription_percent,
'is_active' => $recording->isTranscribing(),
'word_count' => $recording->word_count,
'word_count_display' => $recording->word_count > 0
? number_format($recording->word_count)
: '—',
'badge_color' => match ($recording->transcription_status) {
'done' => 'teal',
'processing', 'pending' => 'amber',
'failed' => 'red',
default => 'zinc',
},
])->values(),
'pendingCount' => $pendingCount,
]))"
@if ($hasActiveTranscriptions)
wire:poll.2s.visible
@endif
x-data="recordingsIndex()"
x-init="
start();
return () => destroy();
@@ -39,34 +22,64 @@
class="min-w-[16rem]"
/>
</div>
<div x-show="pendingCount > 0" x-cloak>
<flux:button
type="button"
variant="ghost"
size="sm"
wire:click="queuePending"
>
Queue <span x-text="pendingCount"></span> pending
<span x-text="pendingCount === 1 ? 'transcription' : 'transcriptions'"></span>
</flux:button>
<div class="flex flex-wrap items-center justify-end gap-2">
@if ($pendingCount > 0)
<flux:button
type="button"
variant="ghost"
size="sm"
wire:click="queuePending"
>
Queue {{ $pendingCount }} pending
{{ $pendingCount === 1 ? 'transcription' : 'transcriptions' }}
</flux:button>
@endif
@if ($totalCount > 0)
<flux:modal.trigger name="delete-all-recordings">
<flux:button type="button" variant="danger" size="sm">
Delete all
</flux:button>
</flux:modal.trigger>
@endif
</div>
</div>
</div>
@if ($totalCount > 0)
<flux:modal name="delete-all-recordings" class="max-w-md">
<div class="space-y-6">
<div>
<flux:heading size="lg">Delete all recordings?</flux:heading>
<flux:text class="mt-2">
This permanently removes
{{ $totalCount === 1 ? 'your 1 recording' : "all {$totalCount} recordings" }}
and their audio files. This cannot be undone.
</flux:text>
</div>
<div class="flex justify-end gap-2">
<flux:modal.close>
<flux:button variant="ghost">Cancel</flux:button>
</flux:modal.close>
<flux:button type="button" variant="danger" wire:click="deleteAll">
Delete all
</flux:button>
</div>
</div>
</flux:modal>
@endif
@if ($recordings->isEmpty())
<flux:card class="border-dashed py-16 text-center">
@if (filled($search))
@if (filled($search))
<flux:card class="border-dashed py-16 text-center">
<flux:text>No recordings match {{ $search }}.</flux:text>
<div class="mt-4">
<flux:button variant="ghost" wire:click="$set('search', '')">Clear search</flux:button>
</div>
@else
<flux:text>No recordings yet.</flux:text>
<div class="mt-4">
<flux:link href="{{ route('recordings.create') }}" wire:navigate>Upload your first MP3</flux:link>
</div>
@endif
</flux:card>
</flux:card>
@else
<livewire:upload-recordings :show-cancel="false" />
@endif
@else
<audio
x-ref="player"
@@ -90,7 +103,7 @@
<flux:table.rows>
@foreach ($recordings as $recording)
<flux:table.row wire:key="recording-{{ $recording->id }}">
<flux:table.row wire:key="recording-{{ $recording->id }}-{{ $recording->transcription_status }}-{{ $recording->transcription_percent }}">
<flux:table.cell>
<flux:button
type="button"
@@ -127,10 +140,7 @@
</flux:table.cell>
<flux:table.cell>{{ $recording->duration_formatted }}</flux:table.cell>
<flux:table.cell>
<span
class="tabular-nums"
x-text="row({{ $recording->id }}).word_count_display"
>
<span class="tabular-nums">
{{ $recording->word_count > 0 ? number_format($recording->word_count) : '—' }}
</span>
</flux:table.cell>
@@ -138,17 +148,25 @@
<x-transcription-status-badge
:status="$recording->transcription_status"
:label="$recording->transcriptionStatusLabel()"
:alpine-row="'row('.$recording->id.')'"
/>
<div
class="mt-1 max-w-[14rem] truncate text-xs text-amber-700 dark:text-amber-300"
x-show="row({{ $recording->id }}).is_active && row({{ $recording->id }}).progress"
x-cloak
:title="row({{ $recording->id }}).progress"
>
<span x-text="row({{ $recording->id }}).percent ? (row({{ $recording->id }}).percent + '% · ') : ''"></span>
<span x-text="row({{ $recording->id }}).progress"></span>
</div>
@if ($recording->transcription_status === 'pending' && filled($recording->transcription_progress))
<div
class="mt-1 max-w-[14rem] truncate text-xs text-zinc-500 dark:text-zinc-400"
title="{{ $recording->transcription_progress }}"
>
{{ $recording->transcription_progress }}
</div>
@elseif ($recording->transcription_status === 'processing' && filled($recording->transcription_progress))
<div
class="mt-1 max-w-[14rem] truncate text-xs text-amber-700 dark:text-amber-300"
title="{{ $recording->transcription_progress }}"
>
@if ($recording->transcription_percent)
{{ $recording->transcription_percent }}% ·
@endif
{{ $recording->transcription_progress }}
</div>
@endif
</flux:table.cell>
<flux:table.cell>
{{ $recording->created_at?->format('Y-m-d H:i') }}