feat: update social media handling across components to prioritize tenant-specific links and add banner support in category items

This commit is contained in:
2026-10-01 14:27:15 -03:00
parent 1edbd84be6
commit 96e51b060e
9 changed files with 80 additions and 10 deletions

View File

@@ -69,7 +69,7 @@
<app-store-footer
[currentYear]="currentYear"
[footerSections]="footerSections()"
[socialMedia]="tenant()?.social_media ?? []"
[socialMedia]="tenant()?.tenant_social_media ?? tenant()?.social_media ?? []"
[logoUrl]="tenant()?.footer_logo ?? null"
[backgroundImageUrl]="tenant()?.footer_bg_image ?? null"
[storeName]="tenant()?.nombre ?? null"

View File

@@ -580,6 +580,20 @@ describe('StoreLayoutComponent', () => {
expect(footer.textContent).not.toContain(tenant.address);
});
it('uses commerce social networks in the footer and respects an empty collection', () => {
tenantState.set({ ...tenant, tenant_social_media: [
{ ...tenant.social_media![0], url: 'https://instagram.com/commerce-contact' },
] });
const fixture = TestBed.createComponent(StoreLayoutComponent);
fixture.detectChanges();
const footer = fixture.nativeElement.querySelector('app-store-footer') as HTMLElement;
expect(footer.querySelector('a[href="https://instagram.com/commerce-contact"]')).not.toBeNull();
tenantState.set({ ...tenant, tenant_social_media: [] });
fixture.detectChanges();
expect(footer.querySelector('a[href="https://instagram.com/commerce-contact"]')).toBeNull();
expect(footer.querySelector('a[href="' + tenant.social_media![0].url + '"]')).toBeNull();
});
it('uses only the main phone number in the footer', () => {
tenantState.set({ ...tenant, main_phone_number: {
id: 10, label: 'Principal', number: '3411234567', tel_url: 'tel:3411234567',

View File

@@ -170,6 +170,7 @@ export interface CatalogCategory {
id: number;
nombre: string;
categoria_id: number | null;
banner_url?: string | null;
}
export interface CategoryItemsResponse extends ApiPaginatedResponse<CatalogFeaturedItem[]> {

View File

@@ -161,6 +161,7 @@ export interface Tenant {
allow_ticket_partial_refund?: boolean;
ticket_partial_refund_percentage?: string;
social_media?: SocialMedia[];
tenant_social_media?: SocialMedia[];
menues?: Menu[];
categories?: Category[];
event_categories?: Category[];

View File

@@ -2,7 +2,14 @@
@if (error()) {
<p class="alert text-center mb-0 category-items__alert-error">{{ error() }}</p>
} @else if (results(); as categoryResults) {
<header class="category-items__header mb-5">
@if (categoryResults.category.banner_url; as bannerUrl) {
<img
class="category-items__banner"
[src]="bannerUrl"
[alt]="'Banner de ' + categoryResults.category.nombre"
/>
}
<header class="category-items__header">
<h1 id="category-items-title" class="category-items__title mb-0">
{{ categoryResults.category.nombre }}
</h1>

View File

@@ -1,16 +1,21 @@
.category-items__banner {
display: block;
width: 100%;
height: 170px;
object-fit: cover;
}
.category-items__header {
display: flex;
align-items: center;
justify-content: center;
padding: 16px 28px;
margin: 16px 0;
}
.category-items__title {
font-size: 24px;
font-size: 15px;
font-weight: bold;
line-height: 1.2;
text-align: center;
color:var(--bs-primary)
text-align: left;
text-transform: uppercase;
color: #000;
}
.category-items__alert-error {

View File

@@ -106,6 +106,30 @@ describe('CategoryItemsPageComponent', () => {
expect(productList.layout()).toBe('row');
expect(productList.groupLayout()).toBe('paginated');
expect(Array.isArray(productList.items())).toBe(false);
expect(fixture.nativeElement.querySelector('.category-items__banner')).toBeNull();
});
it('renders the category banner above the title when the API returns its URL', () => {
getCategoryItems.mockReturnValue(
of({
category: {
id: 7,
nombre: 'Remeras',
categoria_id: null,
banner_url: 'https://example.com/banner.jpg',
},
data: [],
}),
);
const fixture = TestBed.createComponent(CategoryItemsPageComponent);
fixture.detectChanges();
const element = fixture.nativeElement as HTMLElement;
const banner = element.querySelector<HTMLImageElement>('.category-items__banner');
expect(banner?.getAttribute('src')).toBe('https://example.com/banner.jpg');
expect(banner?.alt).toBe('Banner de Remeras');
expect(banner?.nextElementSibling?.querySelector('h1')?.textContent).toContain('Remeras');
});
it('reloads the current category page when catalog availability changes', () => {

View File

@@ -138,6 +138,24 @@ describe('ContactPage', () => {
expect(element.textContent).toContain('San Martín 2450');
});
it('uses commerce networks even when the active event has different networks', () => {
tenantState.set({
...tenant,
tenant_social_media: [{ ...tenant.social_media![0], url: 'https://wa.me/commerce' }],
social_media: [{ ...tenant.social_media![0], url: 'https://wa.me/event' }],
});
fixture.detectChanges();
const element = fixture.nativeElement as HTMLElement;
expect(element.querySelector('a[href="https://wa.me/commerce"]')).not.toBeNull();
expect(element.querySelector('a[href="https://wa.me/event"]')).toBeNull();
});
it('does not restore event networks after all commerce networks are removed', () => {
tenantState.set({ ...tenant, tenant_social_media: [] });
fixture.detectChanges();
expect((fixture.nativeElement as HTMLElement).querySelector('a[href="https://wa.me/543412602222"]')).toBeNull();
});
it('passes the tenant map coordinates to the reusable map component', () => {
const element = fixture.nativeElement as HTMLElement;
const map = element.querySelector('app-location-map');

View File

@@ -47,7 +47,7 @@ export class ContactPage {
private buildContactContent(): ContactContent {
const tenant = this.tenantService.tenant();
const socialMedia = (tenant?.social_media ?? []).filter((social) => social.url.trim());
const socialMedia = (tenant?.tenant_social_media ?? tenant?.social_media ?? []).filter((social) => social.url.trim());
const addresses = [...(tenant?.addresses ?? [])].sort(
(a, b) => Number(b.is_main) - Number(a.is_main),
);