diff --git a/database/factories/AddressFactory.php b/database/factories/AddressFactory.php
new file mode 100644
index 0000000..b28d220
--- /dev/null
+++ b/database/factories/AddressFactory.php
@@ -0,0 +1,50 @@
+
+ */
+ public function definition(): array
+ {
+ $type = match (fake()->numberBetween(1, 3)) {
+ 1 => [AddressType::COMPANY_ADDRESS->value],
+ 2 => [AddressType::DEFAULT_ADDRESS->value],
+ 3 => [AddressType::COMPANY_ADDRESS->value, AddressType::DEFAULT_ADDRESS->value],
+ };
+
+ $hasCompanyAddress = in_array(AddressType::COMPANY_ADDRESS->value, $type);
+
+ return [
+ 'recipient' => fake()->name(),
+ 'company_name' => $hasCompanyAddress ? fake()->company() : null,
+ 'company_vat_id' => $hasCompanyAddress ? fake()->numerify('##########') : null,
+ 'street_address' => [
+ fake()->streetAddress(),
+ fake()->streetAddress(),
+ ],
+ 'postal_code' => fake()->postcode(),
+ 'city' => fake()->city(),
+ 'type' => $type,
+ 'country_id' => Country::inRandomOrder()->first()?->id ?? Country::factory()->create()->id,
+ 'user_id' => User::inRandomOrder()->first()?->id ?? User::factory()->create()->id,
+ ];
+ }
+}
diff --git a/database/migrations/2025_06_16_150346_create_user_addresses_table.php b/database/migrations/2025_06_16_150346_create_user_addresses_table.php
new file mode 100644
index 0000000..95cb9bc
--- /dev/null
+++ b/database/migrations/2025_06_16_150346_create_user_addresses_table.php
@@ -0,0 +1,44 @@
+id();
+ $table->string('recipient', 100);
+ $table->string('company_name', 100)
+ ->nullable();
+ $table->string('company_vat_id', 50)
+ ->nullable();
+ $table->json('street_address');
+ $table->string('postal_code', 50);
+ $table->string('city', 100);
+ $table->json('type')->nullable();
+ $table->string('country_id', 2);
+ $table->foreignId('user_id')
+ ->constrained('users')
+ ->cascadeOnDelete();
+ $table->softDeletes();
+ $table->timestamps();
+ $table->foreign('country_id')
+ ->references('id')
+ ->on('world_countries');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('user_addresses');
+ }
+};
diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php
index 499192c..1e6d45d 100644
--- a/database/seeders/UserSeeder.php
+++ b/database/seeders/UserSeeder.php
@@ -4,6 +4,7 @@
use Eclipse\Core\Models\Site;
use Eclipse\Core\Models\User;
+use Eclipse\Core\Models\User\Address;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
@@ -41,6 +42,10 @@ public function run(): void
foreach (Site::all() as $site) {
$user->sites()->attach($site);
+ Address::factory()->create([
+ 'user_id' => $user->id,
+ ]);
+
if (isset($preset['role'])) {
setPermissionsTeamId($site->id);
$user->assignRole($preset['role'])->save();
diff --git a/database/settings/2025_05_08_075602_create_eclipse_settings.php b/database/settings/2025_05_08_075602_create_eclipse_settings.php
index 5cc9fa6..630318a 100644
--- a/database/settings/2025_05_08_075602_create_eclipse_settings.php
+++ b/database/settings/2025_05_08_075602_create_eclipse_settings.php
@@ -7,5 +7,6 @@
public function up(): void
{
$this->migrator->add('eclipse.email_verification', false);
+ $this->migrator->add('eclipse.address_book', false);
}
};
diff --git a/src/Enums/AddressType.php b/src/Enums/AddressType.php
new file mode 100644
index 0000000..8fc65e9
--- /dev/null
+++ b/src/Enums/AddressType.php
@@ -0,0 +1,28 @@
+ 'Default',
+ self::COMPANY_ADDRESS => 'Company'
+ };
+ }
+
+ public function getDescription(): ?string
+ {
+ return match ($this) {
+ self::DEFAULT_ADDRESS => 'This is the default address',
+ self::COMPANY_ADDRESS => 'This is a company address'
+ };
+ }
+}
diff --git a/src/Filament/Pages/ManageEclipse.php b/src/Filament/Pages/ManageEclipse.php
index f41e8fb..da5b976 100644
--- a/src/Filament/Pages/ManageEclipse.php
+++ b/src/Filament/Pages/ManageEclipse.php
@@ -22,6 +22,8 @@ public function form(Form $form): Form
->schema([
Forms\Components\Toggle::make('email_verification')
->label('Enable user email verification'),
+ Forms\Components\Toggle::make('address_book')
+ ->label('Enable address book'),
]);
}
diff --git a/src/Filament/Resources/UserResource.php b/src/Filament/Resources/UserResource.php
index 10d5055..c8a9dc8 100644
--- a/src/Filament/Resources/UserResource.php
+++ b/src/Filament/Resources/UserResource.php
@@ -5,6 +5,7 @@
use BezhanSalleh\FilamentShield\Contracts\HasShieldPermissions;
use Eclipse\Core\Filament\Exports\TableExport;
use Eclipse\Core\Filament\Resources;
+use Eclipse\Core\Filament\Resources\UserResource\RelationManagers\AddressesRelationManager;
use Eclipse\Core\Models\User;
use Filament\Forms;
use Filament\Forms\Components\Actions\Action;
@@ -287,7 +288,7 @@ public static function infolist(Infolist $infolist): Infolist
public static function getRelations(): array
{
return [
- //
+ AddressesRelationManager::class,
];
}
diff --git a/src/Filament/Resources/UserResource/Pages/EditUser.php b/src/Filament/Resources/UserResource/Pages/EditUser.php
index b40ffdf..0219e66 100644
--- a/src/Filament/Resources/UserResource/Pages/EditUser.php
+++ b/src/Filament/Resources/UserResource/Pages/EditUser.php
@@ -10,6 +10,16 @@ class EditUser extends EditRecord
{
protected static string $resource = UserResource::class;
+ public function hasCombinedRelationManagerTabsWithContent(): bool
+ {
+ return true;
+ }
+
+ public function getContentTabLabel(): ?string
+ {
+ return __('Edit User');
+ }
+
protected function getHeaderActions(): array
{
return [
diff --git a/src/Filament/Resources/UserResource/Pages/ViewUser.php b/src/Filament/Resources/UserResource/Pages/ViewUser.php
index d7f450e..39e5d76 100644
--- a/src/Filament/Resources/UserResource/Pages/ViewUser.php
+++ b/src/Filament/Resources/UserResource/Pages/ViewUser.php
@@ -11,6 +11,16 @@ class ViewUser extends ViewRecord
{
protected static string $resource = UserResource::class;
+ public function hasCombinedRelationManagerTabsWithContent(): bool
+ {
+ return true;
+ }
+
+ public function getContentTabLabel(): ?string
+ {
+ return __('View User');
+ }
+
protected function getHeaderActions(): array
{
return [
diff --git a/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php b/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php
new file mode 100644
index 0000000..b21748d
--- /dev/null
+++ b/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php
@@ -0,0 +1,197 @@
+address_book ?? false;
+
+ return $isAddressBookEnabled;
+ }
+
+ public static function getAddressForm(): array
+ {
+ return [
+ Forms\Components\CheckboxList::make('type')
+ ->live()
+ ->default([AddressType::DEFAULT_ADDRESS->value])
+ ->options(AddressType::class)
+ ->columns(2),
+ Forms\Components\TextInput::make('recipient')
+ ->maxLength(100)
+ ->label('Full name')
+ ->required(),
+ Forms\Components\TextInput::make('company_name')
+ ->visible(fn (Get $get): bool => in_array(AddressType::COMPANY_ADDRESS->value, $get('type') ?? []))
+ ->required()
+ ->maxLength(100),
+ Forms\Components\TextInput::make('company_vat_id')
+ ->visible(fn (Get $get): bool => in_array(AddressType::COMPANY_ADDRESS->value, $get('type') ?? []))
+ ->label('Company VAT ID')
+ ->maxLength(50),
+ Forms\Components\Repeater::make('street_address')
+ ->minItems(1)
+ ->maxItems(3)
+ ->required()
+ ->simple(
+ Forms\Components\TextInput::make('street_address')
+ ->maxLength(255)
+ ->required()
+ )
+ ->addActionLabel(__('Add address line')),
+ Forms\Components\Split::make([
+ Forms\Components\TextInput::make('postal_code')
+ ->required()
+ ->maxLength(50),
+ Forms\Components\TextInput::make('city')
+ ->required()
+ ->maxLength(100),
+ ]),
+ Forms\Components\Select::make('country_id')
+ ->required()
+ ->relationship('country', 'name'),
+ ];
+ }
+
+ public static function getAddressInfolist(): array
+ {
+ return [
+ Infolists\Components\Grid::make()->schema([
+ Infolists\Components\TextEntry::make('type')
+ ->badge()
+ ->formatStateUsing(fn ($state) => self::formatAddressTypeLabels($state)),
+ Infolists\Components\TextEntry::make('recipient'),
+ Infolists\Components\TextEntry::make('company_name')
+ ->visible(fn ($record): bool => self::hasCompanyAddress($record->type)),
+ Infolists\Components\TextEntry::make('company_vat_id')
+ ->visible(fn ($record): bool => self::hasCompanyAddress($record->type))
+ ->default('-')
+ ->label('Company VAT ID'),
+ Infolists\Components\TextEntry::make('street_address')
+ ->listWithLineBreaks(),
+ Infolists\Components\TextEntry::make('country')
+ ->formatStateUsing(fn ($state) => "{$state->name} {$state->flag}"),
+ Infolists\Components\Split::make([
+ Infolists\Components\TextEntry::make('postal_code')
+ ->badge()
+ ->color('warning'),
+ Infolists\Components\TextEntry::make('city'),
+ ])->columnSpanFull(),
+ ]),
+ ];
+ }
+
+ private static function hasCompanyAddress($types): bool
+ {
+ if (! is_array($types)) {
+ return false;
+ }
+
+ return collect($types)->contains(function ($type) {
+ return ($type instanceof AddressType && $type === AddressType::COMPANY_ADDRESS) ||
+ $type === AddressType::COMPANY_ADDRESS->value;
+ });
+ }
+
+ private static function formatAddressTypeLabels($state): string
+ {
+ if (is_array($state)) {
+ return collect($state)->map(function ($type) {
+ if (is_string($type)) {
+ return AddressType::from($type)->getLabel();
+ }
+
+ return $type->getLabel();
+ })->join(', ');
+ }
+
+ if (is_string($state)) {
+ return AddressType::from($state)->getLabel();
+ }
+
+ return $state->getLabel();
+ }
+
+ public function table(Table $table): Table
+ {
+ return $table
+ ->modifyQueryUsing(fn (Builder $query) => $query->with(['country']))
+ ->columns([
+ Tables\Columns\TextColumn::make('recipient')
+ ->weight(FontWeight::Bold)
+ ->description(function ($record) {
+ $recipient = [];
+
+ if ($record->company_name) {
+ $recipient[] = $record->company_name;
+ }
+
+ $recipient[] = implode('
', $record->street_address);
+
+ $recipient[] = "{$record->country->flag} {$record->country->name}";
+
+ return new HtmlString(implode('
', $recipient));
+ })
+ ->searchable(['recipient', 'company_name', 'street_address', 'country_id']),
+ Tables\Columns\TextColumn::make('company_vat_id')
+ ->placeholder('-')
+ ->label('Company VAT ID'),
+ Tables\Columns\TextColumn::make('type')
+ ->badge()
+ ->placeholder('-')
+ ->formatStateUsing(fn ($state) => self::formatAddressTypeLabels($state)),
+ ])
+ ->filters([
+ Tables\Filters\SelectFilter::make('type')
+ ->options(AddressType::class)
+ ->query(function (Builder $query, array $data): Builder {
+ if (filled($data['value'])) {
+ return $query->whereJsonContains('type', $data['value']);
+ }
+
+ return $query;
+ }),
+ Tables\Filters\TrashedFilter::make(),
+ ])
+ ->actions([
+ Tables\Actions\ViewAction::make()
+ ->infolist(self::getAddressInfolist()),
+ Tables\Actions\EditAction::make()
+ ->form(self::getAddressForm()),
+ Tables\Actions\DeleteAction::make(),
+ ])
+ ->bulkActions([
+ BulkActionGroup::make([
+ Tables\Actions\DeleteBulkAction::make(),
+ Tables\Actions\ForceDeleteBulkAction::make(),
+ Tables\Actions\RestoreBulkAction::make(),
+ ]),
+ ])
+ ->headerActions([
+ Tables\Actions\CreateAction::make()
+ ->label('Add new address')
+ ->icon('heroicon-o-plus-circle')
+ ->form(self::getAddressForm()),
+ ]);
+ }
+}
diff --git a/src/Models/User.php b/src/Models/User.php
index de62e58..aa067f6 100644
--- a/src/Models/User.php
+++ b/src/Models/User.php
@@ -3,6 +3,7 @@
namespace Eclipse\Core\Models;
use Eclipse\Core\Database\Factories\UserFactory;
+use Eclipse\Core\Models\User\Address;
use Eclipse\Core\Settings\UserSettings;
use Eclipse\World\Models\Country;
use Exception;
@@ -13,6 +14,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
@@ -95,6 +97,11 @@ public function sites()
return $this->belongsToMany(Site::class, 'site_has_user');
}
+ public function addresses(): HasMany
+ {
+ return $this->hasMany(Address::class);
+ }
+
public function country(): BelongsTo
{
return $this->belongsTo(Country::class);
diff --git a/src/Models/User/Address.php b/src/Models/User/Address.php
new file mode 100644
index 0000000..1e2edb8
--- /dev/null
+++ b/src/Models/User/Address.php
@@ -0,0 +1,102 @@
+
+ */
+ protected $fillable = [
+ 'recipient',
+ 'company_name',
+ 'company_vat_id',
+ 'street_address',
+ 'postal_code',
+ 'city',
+ 'type',
+ 'country_id',
+ 'user_id',
+ ];
+
+ /**
+ * Get the attributes that should be cast.
+ *
+ * @return array
+ */
+ protected function casts(): array
+ {
+ return [
+ 'street_address' => 'array',
+ 'type' => 'array',
+ 'user_id' => 'integer',
+ ];
+ }
+
+ public function user(): BelongsTo
+ {
+ return $this->belongsTo(User::class);
+ }
+
+ public function country(): BelongsTo
+ {
+ return $this->belongsTo(Country::class);
+ }
+
+ protected static function newFactory(): AddressFactory
+ {
+ return AddressFactory::new();
+ }
+
+ protected static function booted(): void
+ {
+ static::saving(function (self $address): void {
+ if (! in_array(AddressType::DEFAULT_ADDRESS->value, $address->type)) {
+ return;
+ }
+
+ $otherAddresses = self::where('user_id', $address->user_id)
+ ->where('id', '!=', $address->id ?? 0)
+ ->get(['id', 'type']);
+
+ $addressesToUpdate = $otherAddresses->filter(
+ fn (Model $existingAddress): bool => in_array(AddressType::DEFAULT_ADDRESS->value, $existingAddress->type ?? [])
+ );
+
+ $addressesToUpdate->each(function (Model $existingAddress): void {
+ $newType = array_values(array_diff($existingAddress->type, [AddressType::DEFAULT_ADDRESS->value]));
+
+ $existingAddress->timestamps = false;
+ $existingAddress->updateQuietly([
+ 'type' => $newType,
+ ]);
+ });
+ });
+
+ static::deleted(function (self $address): void {
+ if (! in_array(AddressType::DEFAULT_ADDRESS->value, $address->type)) {
+ return;
+ }
+
+ self::where('user_id', $address->user_id)
+ ->orderBy('created_at', 'asc')
+ ->first(['id', 'type'])
+ ?->updateQuietly([
+ 'type' => array_merge($address->type ?? [], [AddressType::DEFAULT_ADDRESS->value]),
+ ]);
+ });
+ }
+}
diff --git a/src/Settings/EclipseSettings.php b/src/Settings/EclipseSettings.php
index 2a65acd..6463a68 100644
--- a/src/Settings/EclipseSettings.php
+++ b/src/Settings/EclipseSettings.php
@@ -8,6 +8,8 @@ class EclipseSettings extends Settings
{
public bool $email_verification = false;
+ public bool $address_book = false;
+
public static function group(): string
{
return 'eclipse';
diff --git a/tests/Feature/Filament/RelationManagers/AddressesRelationManagerTest.php b/tests/Feature/Filament/RelationManagers/AddressesRelationManagerTest.php
new file mode 100644
index 0000000..53dbe9a
--- /dev/null
+++ b/tests/Feature/Filament/RelationManagers/AddressesRelationManagerTest.php
@@ -0,0 +1,243 @@
+set_up_super_admin_and_tenant();
+ $this->undoRepeaterFake = Repeater::fake();
+});
+
+afterEach(function () {
+ ($this->undoRepeaterFake)();
+});
+
+function prepareFactoryDataForForm(): array
+{
+ $data = Address::factory()->definition();
+ unset($data['user_id']);
+
+ $data['street_address'] = collect($data['street_address'])
+ ->map(fn ($address) => ['street_address' => $address])
+ ->toArray();
+
+ return $data;
+}
+
+it('can render address relation manager', function (): void {
+ livewire(AddressesRelationManager::class, [
+ 'ownerRecord' => $this->superAdmin,
+ 'pageClass' => EditUser::class,
+ ])->assertSuccessful();
+});
+
+it('can create address', function (): void {
+ $data = prepareFactoryDataForForm();
+
+ livewire(AddressesRelationManager::class, [
+ 'ownerRecord' => $this->superAdmin,
+ 'pageClass' => EditUser::class,
+ ])
+ ->callTableAction('create', data: $data)
+ ->assertHasNoTableActionErrors();
+
+ expect($this->superAdmin->addresses()->count())->toBe(1);
+});
+
+it('can edit address', function (): void {
+ $address = Address::factory()->for($this->superAdmin)->create();
+
+ livewire(AddressesRelationManager::class, [
+ 'ownerRecord' => $this->superAdmin,
+ 'pageClass' => EditUser::class,
+ ])
+ ->callTableAction('edit', $address, data: ['recipient' => 'Updated Name'])
+ ->assertHasNoTableActionErrors();
+
+ expect($address->fresh()->recipient)->toBe('Updated Name');
+});
+
+it('can delete address', function (): void {
+ $address = Address::factory()->for($this->superAdmin)->create();
+
+ livewire(AddressesRelationManager::class, [
+ 'ownerRecord' => $this->superAdmin,
+ 'pageClass' => EditUser::class,
+ ])
+ ->callTableAction('delete', $address)
+ ->assertHasNoTableActionErrors();
+
+ expect($this->superAdmin->fresh()->addresses)->toHaveCount(0);
+});
+
+it('can view address', function (): void {
+ $address = Address::factory()->for($this->superAdmin)->create();
+
+ livewire(AddressesRelationManager::class, [
+ 'ownerRecord' => $this->superAdmin,
+ 'pageClass' => EditUser::class,
+ ])
+ ->callTableAction('view', $address)
+ ->assertHasNoTableActionErrors();
+});
+
+it('can bulk delete addresses', function (): void {
+ $addresses = Address::factory()->count(3)->for($this->superAdmin)->create();
+
+ livewire(AddressesRelationManager::class, [
+ 'ownerRecord' => $this->superAdmin,
+ 'pageClass' => EditUser::class,
+ ])
+ ->callTableBulkAction('delete', $addresses)
+ ->assertHasNoTableBulkActionErrors();
+
+ expect($this->superAdmin->fresh()->addresses)->toHaveCount(0);
+});
+
+it('can soft delete and restore address', function (): void {
+ $address = Address::factory()->for($this->superAdmin)->create();
+
+ livewire(AddressesRelationManager::class, [
+ 'ownerRecord' => $this->superAdmin,
+ 'pageClass' => EditUser::class,
+ ])
+ ->callTableBulkAction('delete', [$address])
+ ->assertHasNoTableBulkActionErrors();
+
+ expect($address->fresh()->trashed())->toBeTrue();
+ expect($this->superAdmin->addresses()->count())->toBe(0); // Active count
+ expect($this->superAdmin->addresses()->withTrashed()->count())->toBe(1); // Total count
+
+ livewire(AddressesRelationManager::class, [
+ 'ownerRecord' => $this->superAdmin,
+ 'pageClass' => EditUser::class,
+ ])
+ ->filterTable('trashed', 'with')
+ ->callTableBulkAction('restore', [$address])
+ ->assertHasNoTableBulkActionErrors();
+
+ expect($address->fresh()->trashed())->toBeFalse();
+ expect($this->superAdmin->addresses()->count())->toBe(1);
+});
+
+it('can force delete address', function (): void {
+ $address = Address::factory()->for($this->superAdmin)->create();
+
+ $address->delete();
+
+ livewire(AddressesRelationManager::class, [
+ 'ownerRecord' => $this->superAdmin,
+ 'pageClass' => EditUser::class,
+ ])
+ ->filterTable('trashed', 'only')
+ ->callTableBulkAction('forceDelete', [$address])
+ ->assertHasNoTableBulkActionErrors();
+
+ expect(Address::withTrashed()->find($address->id))->toBeNull();
+});
+
+it('can filter addresses by type', function (): void {
+ livewire(AddressesRelationManager::class, [
+ 'ownerRecord' => $this->superAdmin,
+ 'pageClass' => EditUser::class,
+ ])
+ ->filterTable('type', AddressType::DEFAULT_ADDRESS->value)
+ ->assertSuccessful();
+});
+
+it('each user can edit only his own addresses', function (): void {
+ $otherUser = User::factory()->create();
+ $otherUserAddress = Address::factory()->for($otherUser)->create();
+
+ $userAddress = Address::factory()->for($this->superAdmin)->create();
+
+ livewire(AddressesRelationManager::class, [
+ 'ownerRecord' => $this->superAdmin,
+ 'pageClass' => EditUser::class,
+ ])
+ ->assertCountTableRecords(1)
+ ->assertSeeText($userAddress->recipient)
+ ->assertDontSeeText($otherUserAddress->recipient);
+
+ livewire(AddressesRelationManager::class, [
+ 'ownerRecord' => $otherUser,
+ 'pageClass' => EditUser::class,
+ ])
+ ->assertCountTableRecords(1)
+ ->assertSeeText($otherUserAddress->recipient)
+ ->assertDontSeeText($userAddress->recipient);
+});
+
+it('admins with user update permission can edit addresses for any user', function (): void {
+ $regularUser = User::factory()->create();
+ $regularUserAddress = Address::factory()->for($regularUser)->create();
+
+ livewire(AddressesRelationManager::class, [
+ 'ownerRecord' => $regularUser,
+ 'pageClass' => EditUser::class,
+ ])
+ ->callTableAction('edit', $regularUserAddress, data: ['recipient' => 'Admin Updated'])
+ ->assertHasNoTableActionErrors();
+
+ expect($regularUserAddress->fresh()->recipient)->toBe('Admin Updated');
+});
+
+it('only one address can be default - new default unsets old one', function (): void {
+ $firstAddress = Address::factory()->for($this->superAdmin)->create([
+ 'type' => [AddressType::DEFAULT_ADDRESS->value],
+ ]);
+
+ $secondAddress = Address::factory()->for($this->superAdmin)->create([
+ 'type' => [AddressType::COMPANY_ADDRESS->value],
+ ]);
+
+ $secondAddress->type = [AddressType::DEFAULT_ADDRESS->value];
+ $secondAddress->save();
+
+ $firstRefreshed = $firstAddress->fresh();
+ $secondRefreshed = $secondAddress->fresh();
+
+ $hasDefault = in_array(AddressType::DEFAULT_ADDRESS->value, $secondRefreshed->type ?? []);
+
+ expect($hasDefault)->toBeTrue('Manual check should pass');
+
+ expect($secondRefreshed->type)->toContain('default_address');
+
+ assertContains(AddressType::DEFAULT_ADDRESS->value, $secondRefreshed->type, 'PHPUnit assertion should work');
+
+ expect($firstRefreshed->type)->not->toContain(AddressType::DEFAULT_ADDRESS->value);
+
+ $defaultCount = $this->superAdmin->addresses()->get()->filter(function ($address) {
+ return in_array(AddressType::DEFAULT_ADDRESS->value, $address->type ?? []);
+ })->count();
+
+ expect($defaultCount)->toBe(1, 'Should have exactly one default address');
+});
+
+it('when deleting default address the oldest becomes default', function (): void {
+ $oldestAddress = Address::factory()->for($this->superAdmin)->create([
+ 'type' => [AddressType::COMPANY_ADDRESS->value],
+ 'created_at' => now()->subDays(5),
+ ]);
+
+ $defaultAddress = Address::factory()->for($this->superAdmin)->create([
+ 'type' => [AddressType::DEFAULT_ADDRESS->value],
+ 'created_at' => now(),
+ ]);
+
+ livewire(AddressesRelationManager::class, [
+ 'ownerRecord' => $this->superAdmin,
+ 'pageClass' => EditUser::class,
+ ])
+ ->callTableAction('delete', $defaultAddress)
+ ->assertHasNoTableActionErrors();
+
+ expect($oldestAddress->fresh()->type)->toContain(AddressType::DEFAULT_ADDRESS->value);
+});