feat(mailbox): Implement cinematic UX and user tiers

- Added Spatie roles (free, pro, enterprise, admin) and access scopes
- Implemented delayed, cinematic mailbox provisioning animation
- Fixed GSAP and SVG collision issues on creation overlay
- Improved component sync with livewire refresh
- Added feature tests for tier systems and fixed RegistrationTest
This commit is contained in:
idevakk
2026-03-06 00:22:27 +05:30
parent 60b87a3609
commit 7dc89880a7
10 changed files with 497 additions and 62 deletions

View File

@@ -35,6 +35,7 @@ class Register extends Component
$validated['password'] = Hash::make($validated['password']);
event(new Registered(($user = User::create($validated))));
$user->assignRole('free');
Auth::login($user);

View File

@@ -38,6 +38,8 @@ class Mailbox extends Component
public $customDomain = '';
public bool $isCreatingFirstMailbox = false;
public function mount()
{
$this->customDomain = Domain::where('is_active', true)->first()?->name ?? 'imail.app';
@@ -49,6 +51,11 @@ class Mailbox extends Component
} else {
$this->currentMailboxId = $this->getActiveMailboxesProperty()->first()?->id;
}
// Auto-create a random mailbox if none exist (first-time visitor)
if ($this->getActiveMailboxesProperty()->isEmpty()) {
$this->isCreatingFirstMailbox = true;
}
}
public function getActiveMailboxesProperty()
@@ -66,7 +73,11 @@ class Mailbox extends Component
public function getAvailableDomainsProperty()
{
return Domain::where('is_active', true)->where('is_archived', false)->get();
return Domain::query()
->where('is_active', true)
->where('is_archived', false)
->accessibleBy(auth()->user())
->get();
}
/**
@@ -116,6 +127,19 @@ class Mailbox extends Component
->paginate(10);
}
public function getUnreadCountProperty(): int
{
$currentMailbox = $this->active_mailboxes->firstWhere('id', $this->currentMailboxId);
if (! $currentMailbox) {
return 0;
}
return Email::where('recipient_email', $currentMailbox->address)
->where('is_read', false)
->count();
}
public function selectEmail($id)
{
$this->selectedEmailId = $id;
@@ -176,7 +200,11 @@ class Mailbox extends Component
public function createMailbox()
{
$domainModel = Domain::where('name', $this->customDomain)->first();
$domainModel = Domain::where('name', $this->customDomain)
->where('is_active', true)
->accessibleBy(auth()->user())
->first();
if (! $domainModel) {
return;
}
@@ -204,6 +232,55 @@ class Mailbox extends Component
Session::put('last_mailbox_id', $mailbox->id);
}
/**
* Auto-create a random mailbox on a public domain.
* Called when no mailboxes exist (first visit, or all deleted).
*/
public function autoCreateRandomMailbox(): void
{
$domain = Domain::query()
->where('is_active', true)
->where('is_archived', false)
->accessibleBy(auth()->user())
->whereJsonContains('allowed_types', 'public')
->inRandomOrder()
->first();
if (! $domain) {
return;
}
$address = fake()->userName().'_'.rand(10, 99).'@'.$domain->name;
$mailbox = MailboxModel::create([
'mailbox_hash' => bin2hex(random_bytes(32)),
'domain_hash' => $domain->domain_hash,
'user_id' => auth()->id(),
'session_id' => Session::getId(),
'address' => $address,
'type' => 'public',
'created_ip' => request()->ip(),
'last_accessed_ip' => request()->ip(),
'last_accessed_at' => now(),
'expires_at' => now()->addDays(7),
]);
$this->currentMailboxId = $mailbox->id;
$this->isCreatingFirstMailbox = false;
Session::put('current_mailbox_id', $mailbox->id);
}
public function finishAutoCreation(): void
{
if ($this->getActiveMailboxesProperty()->isEmpty()) {
$this->autoCreateRandomMailbox();
}
$this->isCreatingFirstMailbox = false;
// Ensure the component re-renders fully with the new data
$this->dispatch('$refresh');
}
public function deleteMailbox($id)
{
$mailbox = MailboxModel::find($id);
@@ -216,6 +293,22 @@ class Mailbox extends Component
$this->selectedEmailId = null;
session(['current_mailbox_id' => $this->currentMailboxId]);
}
// If all mailboxes deleted, auto-create a new random one
// Use a direct query or re-fetch the property to avoid cache issues
$hasActive = MailboxModel::query()
->where(function ($query) {
$query->where('session_id', Session::getId());
if (auth()->check()) {
$query->orWhere('user_id', auth()->id());
}
})
->where('is_blocked', false)
->exists();
if (! $hasActive) {
$this->isCreatingFirstMailbox = true;
}
}
public function downloadEmail($id)
@@ -296,6 +389,7 @@ class Mailbox extends Component
return view('livewire.mailbox', [
'emails' => $this->getEmailsProperty(),
'currentMailbox' => $currentMailbox,
'unreadCount' => $this->unread_count,
]);
}
}