feat(mailbox): Implement tier-based dynamic expiration with real-time Alpine.js countdown
This commit is contained in:
@@ -223,7 +223,7 @@ class Mailbox extends Component
|
||||
'created_ip' => request()->ip(),
|
||||
'last_accessed_ip' => request()->ip(),
|
||||
'last_accessed_at' => now(),
|
||||
'expires_at' => now()->addDays(7), // Default expiry
|
||||
'expires_at' => now()->addDays($this->getValidityDays()),
|
||||
]);
|
||||
|
||||
$this->currentMailboxId = $mailbox->id;
|
||||
@@ -262,7 +262,7 @@ class Mailbox extends Component
|
||||
'created_ip' => request()->ip(),
|
||||
'last_accessed_ip' => request()->ip(),
|
||||
'last_accessed_at' => now(),
|
||||
'expires_at' => now()->addDays(7),
|
||||
'expires_at' => now()->addDays($this->getValidityDays()),
|
||||
]);
|
||||
|
||||
$this->currentMailboxId = $mailbox->id;
|
||||
@@ -270,6 +270,27 @@ class Mailbox extends Component
|
||||
Session::put('current_mailbox_id', $mailbox->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mailbox lifespan in days based on user tier.
|
||||
* Guest: 1 day, Free: 3 days, Pro: 7 days, Enterprise: 14 days.
|
||||
*/
|
||||
protected function getValidityDays(): int
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if (! $user) {
|
||||
return 1; // Guests get 24 hours
|
||||
}
|
||||
|
||||
return match (true) {
|
||||
$user->isEnterprise() => 14,
|
||||
$user->isAdmin() => 14, // Assuming admins get enterprise limits
|
||||
$user->isPro() => 7,
|
||||
$user->isFree() => 3,
|
||||
default => 3,
|
||||
};
|
||||
}
|
||||
|
||||
public function finishAutoCreation(): void
|
||||
{
|
||||
if ($this->getActiveMailboxesProperty()->isEmpty()) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -86,4 +86,39 @@ class UserTierTest extends TestCase
|
||||
$this->assertTrue($accessibleDomains->contains($publicDomain));
|
||||
$this->assertFalse($accessibleDomains->contains($premiumDomain));
|
||||
}
|
||||
|
||||
public function test_mailbox_expiration_is_based_on_user_tier()
|
||||
{
|
||||
$domain = Domain::factory()->create(['allowed_types' => ['public'], 'name' => 'public.com']);
|
||||
|
||||
// Test Guest (1 day)
|
||||
\Livewire\Livewire::test(\App\Livewire\Mailbox::class)
|
||||
->call('autoCreateRandomMailbox');
|
||||
$guestMailbox = \App\Models\Mailbox::latest()->first();
|
||||
$this->assertEquals(1, round(now()->diffInDays($guestMailbox->expires_at)));
|
||||
|
||||
// Test Free (3 days)
|
||||
$freeUser = User::factory()->free()->create();
|
||||
$this->actingAs($freeUser);
|
||||
\Livewire\Livewire::test(\App\Livewire\Mailbox::class)
|
||||
->call('autoCreateRandomMailbox');
|
||||
$freeMailbox = \App\Models\Mailbox::where('user_id', $freeUser->id)->first();
|
||||
$this->assertEquals(3, round(now()->diffInDays($freeMailbox->expires_at)));
|
||||
|
||||
// Test Pro (7 days)
|
||||
$proUser = User::factory()->pro()->create();
|
||||
$this->actingAs($proUser);
|
||||
\Livewire\Livewire::test(\App\Livewire\Mailbox::class)
|
||||
->call('autoCreateRandomMailbox');
|
||||
$proMailbox = \App\Models\Mailbox::where('user_id', $proUser->id)->first();
|
||||
$this->assertEquals(7, round(now()->diffInDays($proMailbox->expires_at)));
|
||||
|
||||
// Test Enterprise (14 days)
|
||||
$entUser = User::factory()->enterprise()->create();
|
||||
$this->actingAs($entUser);
|
||||
\Livewire\Livewire::test(\App\Livewire\Mailbox::class)
|
||||
->call('autoCreateRandomMailbox');
|
||||
$entMailbox = \App\Models\Mailbox::where('user_id', $entUser->id)->first();
|
||||
$this->assertEquals(14, round(now()->diffInDays($entMailbox->expires_at)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user