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

@@ -2,6 +2,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -41,4 +42,27 @@ class Domain extends Model
{
return $this->hasMany(Mailbox::class, 'domain_hash', 'domain_hash');
}
/**
* Scope to domains accessible by the given user tier.
* For guests (null user), only 'public' domains are returned.
*
* Uses MySQL's JSON_CONTAINS to check if the domain's `allowed_types`
* array includes ANY of the user's allowed types.
*
* Usage:
* Domain::accessibleBy(auth()->user())->get(); // logged-in
* Domain::accessibleBy(null)->get(); // guest
* Domain::accessibleBy($user)->where('is_active', true)->get();
*/
public function scopeAccessibleBy(Builder $query, ?User $user = null): Builder
{
$types = $user ? $user->allowedDomainTypes() : User::guestDomainTypes();
return $query->where(function (Builder $q) use ($types) {
foreach ($types as $type) {
$q->orWhereJsonContains('allowed_types', $type);
}
});
}
}

View File

@@ -2,23 +2,24 @@
namespace App\Models;
use Filament\Auth\MultiFactor\App\Contracts\HasAppAuthentication;
use Filament\Auth\MultiFactor\App\Contracts\HasAppAuthenticationRecovery;
use Filament\Auth\MultiFactor\Email\Contracts\HasEmailAuthentication;
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Str;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Spatie\Permission\Traits\HasRoles;
use Filament\Auth\MultiFactor\App\Contracts\HasAppAuthentication;
class User extends Authenticatable implements FilamentUser, HasAppAuthentication, HasAppAuthenticationRecovery, HasEmailAuthentication, MustVerifyEmail
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable, TwoFactorAuthenticatable, HasRoles;
use HasFactory, HasRoles, Notifiable, TwoFactorAuthenticatable;
/**
* The attributes that are mass assignable.
@@ -114,4 +115,102 @@ class User extends Authenticatable implements FilamentUser, HasAppAuthentication
$this->has_email_authentication = $condition;
$this->save();
}
// ─── Tier Checking Helpers ───────────────────────────────────
public function isFree(): bool
{
return $this->hasRole('free');
}
public function isPro(): bool
{
return $this->hasRole('pro');
}
public function isEnterprise(): bool
{
return $this->hasRole('enterprise');
}
public function isAdmin(): bool
{
return $this->hasRole('admin');
}
/**
* Returns the domain `allowed_types` values this user's tier can access.
* Used by Domain::scopeAccessibleBy() to filter domains.
*
* Mapping:
* free ['public']
* pro ['public', 'custom', 'premium']
* enterprise ['public', 'custom', 'premium', 'private']
* admin ['public', 'custom', 'premium', 'private']
*
* @return array<string>
*/
public function allowedDomainTypes(): array
{
return match (true) {
$this->isAdmin() => ['public', 'custom', 'premium', 'private'],
$this->isEnterprise() => ['public', 'custom', 'premium', 'private'],
$this->isPro() => ['public', 'custom', 'premium'],
default => ['public'], // free or no role
};
}
/**
* Domain types accessible by guest (non-authenticated) users.
* Called when auth()->user() is null.
*
* @return array<string>
*/
public static function guestDomainTypes(): array
{
return ['public'];
}
/**
* Human-readable tier label for sidebar display.
* Returns UPPERCASE string like "FREE", "PRO", "ADMIN".
*/
public function tierLabel(): string
{
return match (true) {
$this->isAdmin() => 'ADMIN',
$this->isEnterprise() => 'ENTERPRISE',
$this->isPro() => 'PRO',
default => 'FREE',
};
}
// ─── Eloquent Scopes ─────────────────────────────────────────
/**
* Scope to users with the 'free' Spatie role.
* Usage: User::free()->get()
*/
public function scopeFree(Builder $query): Builder
{
return $query->role('free');
}
/**
* Scope to users with the 'pro' Spatie role.
* Usage: User::pro()->get()
*/
public function scopePro(Builder $query): Builder
{
return $query->role('pro');
}
/**
* Scope to users with the 'enterprise' Spatie role.
* Usage: User::enterprise()->get()
*/
public function scopeEnterprise(Builder $query): Builder
{
return $query->role('enterprise');
}
}