diff --git a/app/Filament/Resources/Domains/DomainResource.php b/app/Filament/Resources/Domains/DomainResource.php new file mode 100644 index 0000000..0018d85 --- /dev/null +++ b/app/Filament/Resources/Domains/DomainResource.php @@ -0,0 +1,61 @@ + ListDomains::route('/'), + 'create' => CreateDomain::route('/create'), + 'edit' => EditDomain::route('/{record}/edit'), + ]; + } + + public static function getRecordRouteBindingEloquentQuery(): Builder + { + return parent::getRecordRouteBindingEloquentQuery() + ->withoutGlobalScopes([ + SoftDeletingScope::class, + ]); + } +} diff --git a/app/Filament/Resources/Domains/Pages/CreateDomain.php b/app/Filament/Resources/Domains/Pages/CreateDomain.php new file mode 100644 index 0000000..178edad --- /dev/null +++ b/app/Filament/Resources/Domains/Pages/CreateDomain.php @@ -0,0 +1,11 @@ +components([ + TextInput::make('name') + ->columnSpan(1) + ->helperText('Domain name: example.com') + ->required(), + TextInput::make('daily_mailbox_limit') + ->integer() + ->minValue(1) + ->default(100) + ->helperText('How many mailboxes can be created with this domain daily') + ->columnSpan(1) + ->required(), + ToggleButtons::make('is_active') + ->options([ + true => 'Active', + false => 'Disabled', + ]) + ->inline() + ->default(true) + ->columnSpanFull() + ->required(), + Select::make('domain_type') + ->options(DomainType::class) + ->enum(DomainType::class) + ->required(), + Select::make('provider_type') + ->options(ProviderType::class) + ->enum(ProviderType::class) + ->required(), + DateTimePicker::make('starts_at'), + DateTimePicker::make('ends_at'), + TextEntry::make('last_used_at') + ->label('Last Used At') + ->formatStateUsing(fn ($state) => $state ? $state->diffForHumans() : 'Never') + ->visible(fn ($context) => $context === 'edit'), + TextEntry::make('checked_at') + ->label('Last Checked At') + ->formatStateUsing(fn ($state) => $state ? $state->diffForHumans() : 'Never') + ->visible(fn ($context) => $context === 'edit'), + TextEntry::make('deleted_at') + ->label('Deleted At') + ->formatStateUsing(fn ($state) => $state ? $state->diffForHumans() : null) + ->color('danger') + ->icon('heroicon-o-trash') + ->visible(fn ($record) => $record?->deleted_at !== null), + + ])->columns(2); + } +} diff --git a/app/Filament/Resources/Domains/Tables/DomainsTable.php b/app/Filament/Resources/Domains/Tables/DomainsTable.php new file mode 100644 index 0000000..7b064e7 --- /dev/null +++ b/app/Filament/Resources/Domains/Tables/DomainsTable.php @@ -0,0 +1,107 @@ +columns([ + TextColumn::make('name') + ->label('Domain') + ->searchable() + ->weight('medium') + ->icon('heroicon-o-globe-alt') + ->copyable() + ->copyMessage('Domain copied!') + ->copyMessageDuration(1500), + ToggleColumn::make('is_active') + ->label('Active') + ->alignCenter(), + TextColumn::make('domain_type') + ->label('Type') + ->formatStateUsing(fn ($state) => $state ? DomainType::tryFrom($state)?->getLabel() : '-') + ->badge() + ->color(fn ($state) => $state ? DomainType::tryFrom($state)?->getColor() : 'gray') + ->alignCenter(), + TextColumn::make('provider_type') + ->label('Provider') + ->formatStateUsing(fn ($state) => $state ? ProviderType::tryFrom($state)?->getLabel() : '-') + ->badge() + ->color(fn ($state) => $state ? ProviderType::tryFrom($state)?->getColor() : 'gray') + ->alignCenter(), + TextColumn::make('daily_mailbox_limit') + ->label('Daily Limit') + ->numeric() + ->formatStateUsing(fn ($state) => number_format($state)) + ->alignCenter() + ->icon('heroicon-o-inbox'), + TextColumn::make('last_used_at') + ->label('Last Used') + ->dateTime('M j, Y g:i A') + ->placeholder('Never') + ->sortable() + ->since() + ->alignCenter(), + TextColumn::make('checked_at') + ->label('Checked') + ->dateTime('M j, Y') + ->placeholder('Never') + ->sortable() + ->since() + ->alignCenter() + ->toggleable(isToggledHiddenByDefault: true), + + ]) + ->filters([ + SelectFilter::make('domain_type') + ->label('Domain Type') + ->options(DomainType::class), + SelectFilter::make('provider_type') + ->label('Provider Type') + ->options(ProviderType::class), + SelectFilter::make('is_active') + ->label('Status') + ->options([ + '1' => 'Active', + '0' => 'Inactive', + ]), + TrashedFilter::make(), + ]) + ->recordActions([ + EditAction::make(), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + DeleteBulkAction::make(), + ForceDeleteBulkAction::make(), + RestoreBulkAction::make(), + ]), + ]) + ->emptyStateHeading('No domains found') + ->emptyStateDescription('Get started by creating your first domain.') + ->emptyStateActions([ + // Add create action if needed + ]) + ->poll('60s') + ->striped() + ->defaultPaginationPageOption(10) + ->paginated([10, 25, 50, 100]) + ->reorderable('sort_order') + ->defaultSort('name'); + } +} diff --git a/app/Models/Domain.php b/app/Models/Domain.php new file mode 100644 index 0000000..8a2d603 --- /dev/null +++ b/app/Models/Domain.php @@ -0,0 +1,71 @@ + 'boolean', + 'daily_mailbox_limit' => 'integer', + 'starts_at' => 'datetime', + 'ends_at' => 'datetime', + 'last_used_at' => 'datetime', + 'checked_at' => 'datetime', + ]; + } + + /** + * Retrieve active domains by type and provider. + * + * @param DomainType|null $domainType Filter by domain type + * @param ProviderType|null $providerType Filter by provider type + * @return array Array of domain names + */ + public static function getActiveDomainsByType( + ?DomainType $domainType = null, + ?ProviderType $providerType = null + ): array { + $query = static::query() + ->where('is_active', true) + ->where(function ($query) { + $query->whereNull('starts_at') + ->orWhere('starts_at', '<=', now()); + }) + ->where(function ($query) { + $query->whereNull('ends_at') + ->orWhere('ends_at', '>=', now()); + }); + + if ($domainType) { + $query->where('domain_type', $domainType->value); + } + + if ($providerType) { + $query->where('provider_type', $providerType->value); + } + + return $query->pluck('name')->toArray(); + } +} diff --git a/app/enum/DomainType.php b/app/enum/DomainType.php new file mode 100644 index 0000000..3a7306e --- /dev/null +++ b/app/enum/DomainType.php @@ -0,0 +1,25 @@ + 'warning', + self::PREMIUM => 'success', + }; + } + + public function getLabel(): string + { + return match ($this) { + self::PUBLIC => 'Public', + self::PREMIUM => 'Premium', + }; + } +} diff --git a/app/enum/ProviderType.php b/app/enum/ProviderType.php new file mode 100644 index 0000000..517b3a5 --- /dev/null +++ b/app/enum/ProviderType.php @@ -0,0 +1,31 @@ + 'danger', + self::YAHOO => 'primary', + self::OUTLOOK => 'info', + self::CUSTOM => 'success', + }; + } + + public function getLabel(): string + { + return match ($this) { + self::GMAIL => 'Gmail', + self::YAHOO => 'Yahoo', + self::OUTLOOK => 'Outlook', + self::CUSTOM => 'Custom', + }; + } +} diff --git a/database/factories/DomainFactory.php b/database/factories/DomainFactory.php new file mode 100644 index 0000000..00d7bb2 --- /dev/null +++ b/database/factories/DomainFactory.php @@ -0,0 +1,37 @@ + + */ +class DomainFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + $domain = $this->faker->unique()->domainName(); + $startDate = $this->faker->optional()->dateTimeBetween('-6 months', 'now'); + $endDate = $startDate ? + $this->faker->optional()->dateTimeBetween($startDate, '+1 year') : + $this->faker->optional()->dateTimeBetween('now', '+1 year'); + + return [ + 'name' => $domain, + 'is_active' => true, + 'daily_mailbox_limit' => $this->faker->numberBetween(50, 500), + 'domain_type' => $this->faker->randomElement(['disposable', 'temporary', 'custom']), + 'provider_type' => $this->faker->randomElement(['internal', 'external', 'partner']), + 'starts_at' => $startDate, + 'ends_at' => $endDate, + 'last_used_at' => $this->faker->optional()->dateTimeBetween('-1 month', 'now'), + 'checked_at' => $this->faker->optional()->dateTimeBetween('-1 week', 'now'), + ]; + } +} diff --git a/database/migrations/2025_11_14_161642_create_domains_table.php b/database/migrations/2025_11_14_161642_create_domains_table.php new file mode 100644 index 0000000..1b636ca --- /dev/null +++ b/database/migrations/2025_11_14_161642_create_domains_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('name')->unique(); + $table->boolean('is_active')->default(true); + $table->integer('daily_mailbox_limit')->default(100); + $table->string('domain_type')->nullable(); + $table->string('provider_type')->nullable(); + $table->timestamp('starts_at')->nullable(); + $table->timestamp('ends_at')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamp('checked_at')->nullable(); + $table->softDeletes(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('domains'); + } +}; diff --git a/database/seeders/DomainSeeder.php b/database/seeders/DomainSeeder.php new file mode 100644 index 0000000..018c5b8 --- /dev/null +++ b/database/seeders/DomainSeeder.php @@ -0,0 +1,19 @@ +count(20) + ->create(); + } +}