diff --git a/app/Domains/Tenant/Controllers/AdminApp/BootstrapAdminAppController.php b/app/Domains/Tenant/Controllers/AdminApp/BootstrapAdminAppController.php index 9f67771..c7df1c2 100644 --- a/app/Domains/Tenant/Controllers/AdminApp/BootstrapAdminAppController.php +++ b/app/Domains/Tenant/Controllers/AdminApp/BootstrapAdminAppController.php @@ -17,7 +17,7 @@ class BootstrapAdminAppController extends Controller return BootstrapAdminAppResource::make( WebsiteType::query() - ->with('siteLogo') + ->with(['siteLogo', 'footerLogo']) ->where('dominio', $domain) ->firstOrFail() ); diff --git a/app/Domains/Tenant/Models/WebsiteType.php b/app/Domains/Tenant/Models/WebsiteType.php index b8376f5..79dcba8 100644 --- a/app/Domains/Tenant/Models/WebsiteType.php +++ b/app/Domains/Tenant/Models/WebsiteType.php @@ -25,6 +25,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; 'border_color', 'login_header_footer_color', 'site_logo', + 'footer_logo', ])] class WebsiteType extends Model { @@ -40,6 +41,14 @@ class WebsiteType extends Model return $this->belongsTo(Attachment::class, 'site_logo'); } + /** + * @return BelongsTo + */ + public function footerLogo(): BelongsTo + { + return $this->belongsTo(Attachment::class, 'footer_logo'); + } + /** * @return HasMany */ diff --git a/app/Domains/Tenant/Resources/AdminApp/BootstrapAdminAppResource.php b/app/Domains/Tenant/Resources/AdminApp/BootstrapAdminAppResource.php index 4cebcfe..8f021fe 100644 --- a/app/Domains/Tenant/Resources/AdminApp/BootstrapAdminAppResource.php +++ b/app/Domains/Tenant/Resources/AdminApp/BootstrapAdminAppResource.php @@ -33,6 +33,7 @@ class BootstrapAdminAppResource extends JsonResource 'border_color' => $this->border_color, 'login_header_footer_color' => $this->login_header_footer_color, 'site_logo' => $this->siteLogo?->getTemporaryUrl(1440), + 'footer_logo' => $this->footerLogo?->getTemporaryUrl(1440), ]; } } diff --git a/app/Domains/Tenant/Services/WebsiteTypeService.php b/app/Domains/Tenant/Services/WebsiteTypeService.php index 3ed20f7..bce4f8b 100644 --- a/app/Domains/Tenant/Services/WebsiteTypeService.php +++ b/app/Domains/Tenant/Services/WebsiteTypeService.php @@ -15,7 +15,7 @@ class WebsiteTypeService ) {} /** - * Create a website type and store its site logo. + * Create a website type and store its logos. * * @param array $data */ @@ -25,7 +25,7 @@ class WebsiteTypeService } /** - * Create or update a website type and replace its site logo when provided. + * Create or update a website type and replace its logos when provided. * * @param array $attributes * @param array $values @@ -44,34 +44,41 @@ class WebsiteTypeService private function save(WebsiteType $websiteType, array $data): WebsiteType { return DB::transaction(function () use ($websiteType, $data): WebsiteType { - $hasSiteLogo = array_key_exists('site_logo', $data); - $siteLogo = $data['site_logo'] ?? null; - $previousSiteLogo = $websiteType->exists - ? $websiteType->siteLogo()->first() - : null; + $previousLogos = []; - unset($data['site_logo']); - - if ($hasSiteLogo) { - $attachment = null; - - if ($siteLogo) { - $attachment = is_string($siteLogo) && Str::isUuid($siteLogo) - ? Attachment::query()->where('key', $siteLogo)->first() - : $this->attachmentService->store($siteLogo, 'website-types'); + foreach (['site_logo' => 'siteLogo', 'footer_logo' => 'footerLogo'] as $field => $relation) { + if (! array_key_exists($field, $data)) { + continue; } - $data['site_logo'] = $attachment?->id; + $logo = $data[$field]; + $previousLogos[$field] = $websiteType->exists + ? $websiteType->{$relation}()->first() + : null; + unset($data[$field]); + + $attachment = null; + + if ($logo) { + $attachment = is_string($logo) && Str::isUuid($logo) + ? Attachment::query()->where('key', $logo)->first() + : $this->attachmentService->store($logo, 'website-types'); + } + + $data[$field] = $attachment?->id; } $websiteType->fill($data)->save(); - if ( - $hasSiteLogo - && $previousSiteLogo instanceof Attachment - && $previousSiteLogo->id !== $websiteType->site_logo - ) { - $this->attachmentService->delete($previousSiteLogo); + foreach ($previousLogos as $field => $previousLogo) { + if ( + $previousLogo instanceof Attachment + && $previousLogo->id !== $websiteType->{$field} + && $previousLogo->id !== $websiteType->site_logo + && $previousLogo->id !== $websiteType->footer_logo + ) { + $this->attachmentService->delete($previousLogo); + } } return $websiteType; diff --git a/database/migrations/2026_07_31_000600_add_footer_logo_to_website_type_table.php b/database/migrations/2026_07_31_000600_add_footer_logo_to_website_type_table.php new file mode 100644 index 0000000..d32663d --- /dev/null +++ b/database/migrations/2026_07_31_000600_add_footer_logo_to_website_type_table.php @@ -0,0 +1,31 @@ +unsignedBigInteger('footer_logo')->nullable()->after('site_logo'); + + $table->foreign('footer_logo') + ->references('id') + ->on('attachments') + ->nullOnDelete(); + }); + } + + public function down(): void + { + Schema::table('website_type', function (Blueprint $table): void { + if (Schema::getConnection()->getDriverName() !== 'sqlite') { + $table->dropForeign(['footer_logo']); + } + + $table->dropColumn('footer_logo'); + }); + } +}; diff --git a/database/seeders/WebsiteTypeSeeder.php b/database/seeders/WebsiteTypeSeeder.php index 71a8c81..95b0889 100644 --- a/database/seeders/WebsiteTypeSeeder.php +++ b/database/seeders/WebsiteTypeSeeder.php @@ -33,6 +33,7 @@ class WebsiteTypeSeeder extends Seeder 'dominio' => 'localhost', ...self::PRESENTATION, 'site_logo' => $this->onTicketLogo(), + 'footer_logo' => $this->onTicketFooterLogo(), ], ); @@ -64,6 +65,7 @@ class WebsiteTypeSeeder extends Seeder 'dominio' => 'onticket.localhost', ...self::PRESENTATION, 'site_logo' => $this->onTicketLogo(), + 'footer_logo' => $this->onTicketFooterLogo(), ], ); @@ -115,7 +117,7 @@ class WebsiteTypeSeeder extends Seeder private function onTicketLogo(): UploadedFile { - $path = public_path('images/website_types/shopit_logo.png'); + $path = public_path('images/website_types/onticket_logo.png'); if (! file_exists($path)) { throw new RuntimeException("OnTicket logo not found at path: {$path}"); @@ -129,4 +131,21 @@ class WebsiteTypeSeeder extends Seeder true, ); } + + private function onTicketFooterLogo(): UploadedFile + { + $path = public_path('images/website_types/onticket_footer_logo.png'); + + if (! file_exists($path)) { + throw new RuntimeException("OnTicket footer logo not found at path: {$path}"); + } + + return new UploadedFile( + $path, + 'onticket_footer_logo.png', + 'image/png', + null, + true, + ); + } } diff --git a/public/images/website_types/onticket_footer_logo.png b/public/images/website_types/onticket_footer_logo.png new file mode 100644 index 0000000..dd86921 Binary files /dev/null and b/public/images/website_types/onticket_footer_logo.png differ diff --git a/public/images/website_types/shopit_logo.png b/public/images/website_types/onticket_logo.png similarity index 100% rename from public/images/website_types/shopit_logo.png rename to public/images/website_types/onticket_logo.png diff --git a/tests/Feature/Seeders/WebsiteTypeSeederTest.php b/tests/Feature/Seeders/WebsiteTypeSeederTest.php index 47a93a6..e2747e3 100644 --- a/tests/Feature/Seeders/WebsiteTypeSeederTest.php +++ b/tests/Feature/Seeders/WebsiteTypeSeederTest.php @@ -21,7 +21,7 @@ class WebsiteTypeSeederTest extends TestCase $this->seed(WebsiteTypeSeeder::class); $this->assertSame(2, WebsiteType::query()->count()); - $this->assertSame(2, Attachment::query()->count()); + $this->assertSame(4, Attachment::query()->count()); $expectedPresentation = [ 'primary_color' => '#FF7006', @@ -46,6 +46,8 @@ class WebsiteTypeSeederTest extends TestCase $this->assertSame($expectedPresentation, $shopIt->only(array_keys($expectedPresentation))); $this->assertSame('onticket_logo.png', $shopIt->siteLogo->filename); Storage::disk('s3')->assertExists($shopIt->siteLogo->path); + $this->assertSame('onticket_footer_logo.png', $shopIt->footerLogo->filename); + Storage::disk('s3')->assertExists($shopIt->footerLogo->path); $this->assertSame(['carousel'], $shopIt->extras->pluck('codigo')->all()); $this->assertSame('Carrusel principal', $shopIt->extras->sole()->nombre); $this->assertSame([ @@ -71,7 +73,10 @@ class WebsiteTypeSeederTest extends TestCase $this->assertSame($expectedPresentation, $onTicket->only(array_keys($expectedPresentation))); $this->assertSame('onticket_logo.png', $onTicket->siteLogo->filename); Storage::disk('s3')->assertExists($onTicket->siteLogo->path); + $this->assertSame('onticket_footer_logo.png', $onTicket->footerLogo->filename); + Storage::disk('s3')->assertExists($onTicket->footerLogo->path); $this->assertNotSame($shopIt->site_logo, $onTicket->site_logo); + $this->assertNotSame($shopIt->footer_logo, $onTicket->footer_logo); $this->assertEqualsCanonicalizing( ['heroConfig', 'eventConfig'], $onTicket->extras->pluck('codigo')->all(), diff --git a/tests/Feature/Tenant/BootstrapAdminAppControllerTest.php b/tests/Feature/Tenant/BootstrapAdminAppControllerTest.php index be5b07a..b97c3e0 100644 --- a/tests/Feature/Tenant/BootstrapAdminAppControllerTest.php +++ b/tests/Feature/Tenant/BootstrapAdminAppControllerTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature\Tenant; +use App\Domains\Attachable\Models\Attachment; use App\Domains\Tenant\Models\WebsiteType; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -12,6 +13,8 @@ class BootstrapAdminAppControllerTest extends TestCase public function test_it_publicly_bootstraps_the_admin_app_by_domain(): void { + $footerLogo = Attachment::factory()->create(); + WebsiteType::query()->create([ 'codigo' => 'shopit', 'nombre' => 'ShopIt', @@ -27,6 +30,7 @@ class BootstrapAdminAppControllerTest extends TestCase 'background_color' => '#f8f8f8', 'border_color' => '#eaeaea', 'login_header_footer_color' => '#313131', + 'footer_logo' => $footerLogo->id, ]); $this->getJson('/api/v1/adminapp/bootstrap/ADMIN.SHOPIT.TEST') @@ -37,7 +41,8 @@ class BootstrapAdminAppControllerTest extends TestCase ->assertJsonPath('data.primary_color', '#112233') ->assertJsonPath('data.warning_color', '#ffaa00') ->assertJsonPath('data.login_header_footer_color', '#313131') - ->assertJsonPath('data.site_logo', null); + ->assertJsonPath('data.site_logo', null) + ->assertJsonPath('data.footer_logo', $footerLogo->getTemporaryUrl(1440)); } public function test_it_returns_not_found_for_an_unknown_domain(): void diff --git a/tests/Feature/Tenant/WebsiteTypeServiceTest.php b/tests/Feature/Tenant/WebsiteTypeServiceTest.php index ea3edd4..d21e808 100644 --- a/tests/Feature/Tenant/WebsiteTypeServiceTest.php +++ b/tests/Feature/Tenant/WebsiteTypeServiceTest.php @@ -12,7 +12,7 @@ class WebsiteTypeServiceTest extends TestCase { use RefreshDatabase; - public function test_it_creates_a_website_type_and_uploads_its_logo_to_s3(): void + public function test_it_creates_a_website_type_and_uploads_its_logos_to_s3(): void { Storage::fake('s3'); @@ -32,14 +32,19 @@ class WebsiteTypeServiceTest extends TestCase 'border_color' => '#dddddd', 'login_header_footer_color' => '#333333', 'site_logo' => UploadedFile::fake()->image('site-logo.png'), + 'footer_logo' => UploadedFile::fake()->image('footer-logo.png'), ]); $siteLogo = $websiteType->siteLogo()->firstOrFail(); + $footerLogo = $websiteType->footerLogo()->firstOrFail(); $this->assertSame('marketplace', $websiteType->codigo); $this->assertSame($siteLogo->id, $websiteType->site_logo); + $this->assertSame($footerLogo->id, $websiteType->footer_logo); $this->assertStringStartsWith('website-types/', $siteLogo->path); + $this->assertStringStartsWith('website-types/', $footerLogo->path); Storage::disk('s3')->assertExists($siteLogo->path); + Storage::disk('s3')->assertExists($footerLogo->path); $this->assertDatabaseHas('website_type', [ 'codigo' => 'marketplace', 'dominio' => 'marketplace.test', @@ -51,6 +56,7 @@ class WebsiteTypeServiceTest extends TestCase 'border_color' => '#dddddd', 'login_header_footer_color' => '#333333', 'site_logo' => $siteLogo->id, + 'footer_logo' => $footerLogo->id, ]); } }