feat(mailbox): Implement tier-based dynamic expiration with real-time Alpine.js countdown

This commit is contained in:
idevakk
2026-03-06 01:33:15 +05:30
parent 3763847dd6
commit e79c3f79a2
3 changed files with 142 additions and 12 deletions

View File

@@ -154,18 +154,58 @@
</div>
</div>
<div class="text-[11px] font-mono text-white break-all mb-4">{{ $currentMailbox->address }}</div>
<div class="space-y-2">
<div class="space-y-2"
x-data="{
expiresAt: '{{ $currentMailbox->expires_at?->toIso8601String() }}',
created_at: '{{ $currentMailbox->created_at?->toIso8601String() }}',
timeLeft: 'Never',
percent: 100,
init() {
if (!this.expiresAt) return;
this.update();
setInterval(() => this.update(), 1000);
},
update() {
const end = new Date(this.expiresAt).getTime();
const start = new Date(this.created_at).getTime();
const now = new Date().getTime();
if (now > end) {
this.timeLeft = 'Expired';
this.percent = 0;
return;
}
// Calculate time string
const diff = end - now;
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const mins = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const secs = Math.floor((diff % (1000 * 60)) / 1000);
if (days > 0) {
this.timeLeft = `${days}d ${hours}h ${mins}m ${secs}s`;
} else if (hours > 0) {
this.timeLeft = `${hours}h ${mins}m ${secs}s`;
} else if (mins > 0) {
this.timeLeft = `${mins}m ${secs}s`;
} else {
this.timeLeft = `${secs}s`;
}
// Calculate percentage based on total lifespan vs remaining
const totalLifespan = end - start;
const timeRemaining = end - now;
this.percent = Math.max(0, Math.min(100, (timeRemaining / totalLifespan) * 100));
}
}">
<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?->diffForHumans(['parts' => 2, 'short' => true]) ?? 'Never' }}</span>
<span class="text-pink-500 font-mono" x-text="timeLeft"></span>
</div>
<div class="h-1 bg-white/5 rounded-full overflow-hidden">
@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 class="h-full bg-gradient-to-r from-pink-500 to-emerald-500 transition-all duration-1000 ease-linear" :style="`width: ${percent}%`"></div>
</div>
</div>
</div>
@@ -191,9 +231,43 @@
@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 cursor-pointer">
<div class="min-w-0 flex-1">
<div class="min-w-0 flex-1"
x-data="{
expiresAt: '{{ $mailbox->expires_at?->toIso8601String() }}',
timeLeft: 'Never',
init() {
if (!this.expiresAt) return;
this.update();
setInterval(() => this.update(), 1000); // 1-sec interval
},
update() {
const end = new Date(this.expiresAt).getTime();
const now = new Date().getTime();
if (now > end) {
this.timeLeft = 'Expired';
return;
}
const diff = end - now;
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const mins = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const secs = Math.floor((diff % (1000 * 60)) / 1000);
if (days > 0) {
this.timeLeft = `${days}d ${hours}h ${mins}m ${secs}s`;
} else if (hours > 0) {
this.timeLeft = `${hours}h ${mins}m ${secs}s`;
} else if (mins > 0) {
this.timeLeft = `${mins}m ${secs}s`;
} else {
this.timeLeft = `${secs}s`;
}
}
}">
<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 class="text-[9px] text-zinc-600 font-bold uppercase mt-1" x-text="timeLeft"></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>