Phase 2: Implement Domain & Mailbox persistence, Analytics, and fix pagination issues
This commit is contained in:
@@ -81,14 +81,14 @@
|
||||
<span class="text-[10px] font-bold text-zinc-500 uppercase tracking-widest">Active Mailbox</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button @click="generateQR('{{ $currentMailbox['address'] }}')"
|
||||
<button @click="generateQR('{{ $currentMailbox->address }}')"
|
||||
class="p-1.5 rounded-lg bg-white/5 text-zinc-500 hover:text-white hover:bg-white/10 transition-all"
|
||||
title="QR Code">
|
||||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="3" width="7" height="7" /><rect x="14" y="3" width="7" height="7" /><rect x="14" y="14" width="7" height="7" /><rect x="3" y="14" width="7" height="7" />
|
||||
</svg>
|
||||
</button>
|
||||
<button @click="navigator.clipboard.writeText('{{ $currentMailbox['address'] }}'); addToast('Address copied to clipboard', 'success')"
|
||||
<button @click="navigator.clipboard.writeText('{{ $currentMailbox->address }}'); addToast('Address copied to clipboard', 'success')"
|
||||
class="p-1.5 rounded-lg bg-white/5 text-zinc-500 hover:text-white hover:bg-white/10 transition-all"
|
||||
title="Copy Address">
|
||||
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /></svg>
|
||||
@@ -98,7 +98,7 @@
|
||||
message: 'Are you sure you want to delete this mailbox? All emails will be permanently lost.',
|
||||
confirmLabel: 'Burn Now',
|
||||
type: 'danger',
|
||||
action: () => $wire.deleteMailbox({{ $currentMailbox['id'] }})
|
||||
action: () => $wire.deleteMailbox({{ $currentMailbox->id }})
|
||||
})"
|
||||
class="p-1.5 rounded-lg bg-rose-500/10 text-rose-500/60 hover:text-rose-500 hover:bg-rose-500/20 transition-all"
|
||||
title="Delete Session">
|
||||
@@ -106,14 +106,19 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-[11px] font-mono text-white break-all mb-4">{{ $currentMailbox['address'] }}</div>
|
||||
<div class="text-[11px] font-mono text-white break-all mb-4">{{ $currentMailbox->address }}</div>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between text-[10px]">
|
||||
<span class="text-zinc-500 uppercase font-black tracking-tighter">Expires In</span>
|
||||
<span class="text-pink-500 font-mono">{{ $currentMailbox['expires_at'] }}</span>
|
||||
<span class="text-pink-500 font-mono">{{ $currentMailbox->expires_at?->diffForHumans(['parts' => 2, 'short' => true]) ?? 'Never' }}</span>
|
||||
</div>
|
||||
<div class="h-1 bg-white/5 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-gradient-to-r from-pink-500 to-emerald-500" style="width: {{ $currentMailbox['progress'] }}%"></div>
|
||||
@php
|
||||
$percent = $currentMailbox->expires_at
|
||||
? max(0, min(100, (now()->diffInSeconds($currentMailbox->expires_at) / (86400 * 7)) * 100))
|
||||
: 100;
|
||||
@endphp
|
||||
<div class="h-full bg-gradient-to-r from-pink-500 to-emerald-500" style="width: {{ $percent }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,13 +139,13 @@
|
||||
<div class="space-y-3">
|
||||
<h4 class="text-[10px] font-bold text-zinc-600 uppercase tracking-[0.2em] px-3 mb-4">Your Sessions</h4>
|
||||
<div class="space-y-2 max-h-48 overflow-y-auto pr-1 scrollbar-hide">
|
||||
@foreach($activeMailboxes as $mailbox)
|
||||
@if($mailbox['id'] !== $currentMailboxId)
|
||||
<button wire:click="switchMailbox({{ $mailbox['id'] }})"
|
||||
@foreach($this->active_mailboxes as $mailbox)
|
||||
@if($mailbox->id !== $currentMailboxId)
|
||||
<button wire:click="switchMailbox({{ $mailbox->id }})"
|
||||
class="w-full p-3 rounded-xl bg-zinc-900/40 border border-white/5 text-left group hover:border-white/20 transition-all flex items-center justify-between">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-[10px] font-mono text-zinc-400 truncate group-hover:text-white transition-colors">{{ $mailbox['address'] }}</div>
|
||||
<div class="text-[9px] text-zinc-600 font-bold uppercase mt-1">{{ $mailbox['expires_at'] }} remaining</div>
|
||||
<div class="text-[10px] font-mono text-zinc-400 truncate group-hover:text-white transition-colors">{{ $mailbox->address }}</div>
|
||||
<div class="text-[9px] text-zinc-600 font-bold uppercase mt-1">{{ $mailbox->expires_at?->diffForHumans(['short' => true]) ?? 'Never' }}</div>
|
||||
</div>
|
||||
<svg class="w-4 h-4 text-zinc-700 group-hover:text-pink-500 translate-x-2 opacity-0 group-hover:translate-x-0 group-hover:opacity-100 transition-all" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
|
||||
</button>
|
||||
@@ -216,38 +221,38 @@
|
||||
<!-- List Content -->
|
||||
<div class="flex-1 overflow-y-auto divide-y divide-white/5 scrollbar-hide" x-ref="listContainer">
|
||||
@foreach($emails as $email)
|
||||
<div wire:key="email-{{ $email['id'] }}"
|
||||
@click="$wire.selectEmail({{ $email['id'] }}); mobileView = 'detail'"
|
||||
<div wire:key="email-{{ $email->id }}"
|
||||
@click="$wire.selectEmail({{ $email->id }}); mobileView = 'detail'"
|
||||
class="p-5 cursor-pointer transition-all relative group"
|
||||
:class="selectedId === {{ $email['id'] }} ? 'bg-pink-500/5' : 'hover:bg-white/[0.02]'">
|
||||
:class="selectedId === {{ $email->id }} ? 'bg-pink-500/5' : 'hover:bg-white/[0.02]'">
|
||||
<div class="absolute left-0 top-0 bottom-0 w-1 bg-pink-500 scale-y-0 transition-transform duration-300"
|
||||
:class="selectedId === {{ $email['id'] }} ? 'scale-y-100' : 'scale-y-0'"></div>
|
||||
:class="selectedId === {{ $email->id }} ? 'scale-y-100' : 'scale-y-0'"></div>
|
||||
|
||||
<div class="flex items-start justify-between gap-4 mb-2">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
@if($email['unread'])
|
||||
@if(!$email->is_read)
|
||||
<div class="w-2 h-2 rounded-full bg-pink-500 shadow-[0_0_8px_rgba(236,72,153,0.5)] flex-shrink-0"></div>
|
||||
@endif
|
||||
<h4 class="text-sm font-bold truncate transition-colors" :class="selectedId === {{ $email['id'] }} ? 'text-white' : 'text-zinc-200 group-hover:text-white'">
|
||||
{{ $email['from_name'] }}
|
||||
<h4 class="text-sm font-bold truncate transition-colors" :class="selectedId === {{ $email->id }} ? 'text-white' : 'text-zinc-200 group-hover:text-white'">
|
||||
{{ $email->sender_name ?: $email->sender_email }}
|
||||
</h4>
|
||||
</div>
|
||||
<span class="text-[10px] font-bold text-zinc-600 uppercase whitespace-nowrap">{{ $email['time'] }}</span>
|
||||
<span class="text-[10px] font-bold text-zinc-600 uppercase whitespace-nowrap">{{ $email->received_at?->diffForHumans(['short' => true]) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="text-xs font-semibold text-zinc-300 mb-2 truncate group-hover:text-zinc-100 transition-colors">
|
||||
{{ $email['subject'] }}
|
||||
{{ $email->subject }}
|
||||
</div>
|
||||
|
||||
<p class="text-[11px] text-zinc-500 line-clamp-2 leading-relaxed font-medium">
|
||||
{{ $email['preview'] }}
|
||||
{{ $email->preview }}
|
||||
</p>
|
||||
|
||||
@if(count($email['attachments']) > 0)
|
||||
@if($email->attachment_size > 0)
|
||||
<div class="mt-3 flex items-center gap-2">
|
||||
<div class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-white/5 border border-white/5 text-[9px] font-mono text-zinc-400">
|
||||
<svg class="w-3 h-3 text-zinc-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"></path></svg>
|
||||
{{ $email['attachments'][0]['name'] }}
|
||||
Attachments
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@@ -260,21 +265,21 @@
|
||||
<button wire:click="previousPage"
|
||||
@click="$refs.listContainer.scrollTop = 0"
|
||||
class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white/5 border border-white/5 text-[10px] font-bold text-zinc-400 hover:text-white hover:bg-white/10 transition-all disabled:opacity-30 disabled:pointer-events-none uppercase tracking-widest"
|
||||
{{ $page <= 1 ? 'disabled' : '' }}>
|
||||
{{ $emails->onFirstPage() ? 'disabled' : '' }}>
|
||||
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /></svg>
|
||||
Prev
|
||||
</button>
|
||||
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-[10px] font-black text-white/90 uppercase tracking-[0.2em]">{{ $page }}</span>
|
||||
<span class="text-[10px] font-black text-white/90 uppercase tracking-[0.2em]">{{ $emails->currentPage() }}</span>
|
||||
<span class="text-[10px] font-bold text-zinc-600 uppercase tracking-widest">/</span>
|
||||
<span class="text-[10px] font-bold text-zinc-500 uppercase tracking-widest">{{ $totalPages }}</span>
|
||||
<span class="text-[10px] font-bold text-zinc-500 uppercase tracking-widest">{{ $emails->lastPage() }}</span>
|
||||
</div>
|
||||
|
||||
<button wire:click="nextPage"
|
||||
@click="$refs.listContainer.scrollTop = 0"
|
||||
class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white/5 border border-white/5 text-[10px] font-bold text-zinc-400 hover:text-white hover:bg-white/10 transition-all disabled:opacity-30 disabled:pointer-events-none uppercase tracking-widest"
|
||||
{{ $page >= $totalPages ? 'disabled' : '' }}>
|
||||
{{ !$emails->hasMorePages() ? 'disabled' : '' }}>
|
||||
Next
|
||||
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
|
||||
</button>
|
||||
@@ -358,7 +363,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@php $currentEmail = $selectedEmailId ? collect($emails)->firstWhere('id', $selectedEmailId) : null; @endphp
|
||||
@php $currentEmail = $selectedEmailId ? $emails->firstWhere('id', $selectedEmailId) : null; @endphp
|
||||
@if($currentEmail)
|
||||
|
||||
<!-- Detail Header -->
|
||||
@@ -412,20 +417,20 @@
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-12">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-pink-500 to-purple-600 flex items-center justify-center text-xl font-bold text-white shadow-xl">
|
||||
{{ substr($currentEmail['from_name'], 0, 1) }}
|
||||
{{ substr($currentEmail->sender_name ?: $currentEmail->sender_email, 0, 1) }}
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-white mb-1">{{ $currentEmail['from_name'] }}</h2>
|
||||
<div class="text-xs font-mono text-zinc-500 uppercase tracking-widest">{{ $currentEmail['from_email'] }}</div>
|
||||
<h2 class="text-xl font-bold text-white mb-1">{{ $currentEmail->sender_name ?: $currentEmail->sender_email }}</h2>
|
||||
<div class="text-xs font-mono text-zinc-500 uppercase tracking-widest">{{ $currentEmail->sender_email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-[10px] font-bold text-zinc-600 uppercase tracking-widest px-3 py-1.5 rounded-full border border-white/5 bg-white/5">
|
||||
Received {{ $currentEmail['time'] }}
|
||||
Received {{ $currentEmail->received_at?->diffForHumans() }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="text-2xl md:text-3xl font-black tracking-tight text-white mb-8 leading-tight">
|
||||
{{ $currentEmail['subject'] }}
|
||||
{{ $currentEmail->subject }}
|
||||
</h1>
|
||||
|
||||
@if($viewMode === 'html')
|
||||
@@ -480,13 +485,13 @@
|
||||
|
||||
<div class="prose prose-invert max-w-none text-zinc-400 leading-relaxed font-medium mb-12 {{ $viewMode === 'text' ? 'whitespace-pre-wrap font-mono text-[13px] shadow-[inset_0_20px_50px_rgba(0,0,0,0.1)] p-8 bg-zinc-900/30 rounded-3xl border border-white/5 tracking-tight' : 'space-y-4' }}">{!! $this->getProcessedContent($currentEmail) !!}</div>
|
||||
|
||||
@if(count($currentEmail['attachments']) > 0)
|
||||
@if($currentEmail->attachment_size > 0 && is_array($currentEmail->attachments_json))
|
||||
<div class="mt-12 pt-8 border-t border-white/5">
|
||||
<h4 class="text-[10px] font-bold text-white uppercase tracking-[0.2em] mb-4">Attachments ({{ count($currentEmail['attachments']) }})</h4>
|
||||
<h4 class="text-[10px] font-bold text-white uppercase tracking-[0.2em] mb-4">Attachments ({{ count($currentEmail->attachments_json) }})</h4>
|
||||
|
||||
<div class="relative group/attachments">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 @guest blur-[3px] pointer-events-none select-none grayscale opacity-60 @endguest">
|
||||
@foreach($currentEmail['attachments'] as $attachment)
|
||||
@foreach($currentEmail->attachments_json as $attachment)
|
||||
<div class="flex items-center justify-between p-4 rounded-2xl bg-white/5 border border-white/5 hover:border-pink-500/30 transition-all group">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-zinc-900 flex items-center justify-center text-pink-500">
|
||||
@@ -602,8 +607,8 @@
|
||||
<div class="relative group">
|
||||
<select wire:model="customDomain"
|
||||
class="w-full h-12 bg-zinc-950 border border-white/5 rounded-2xl px-4 text-sm focus:outline-none focus:ring-1 focus:ring-pink-500/50 transition-all appearance-none text-zinc-300 cursor-pointer">
|
||||
@foreach($availableDomains as $domain)
|
||||
<option value="{{ $domain }}">@ {{ $domain }}</option>
|
||||
@foreach($this->available_domains as $domain)
|
||||
<option value="{{ $domain->name }}">@ {{ $domain->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<svg class="absolute right-4 top-1/2 -translate-y-1/2 w-4 h-4 text-zinc-500 pointer-events-none" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /></svg>
|
||||
|
||||
Reference in New Issue
Block a user