diff --git a/src/app/core/layout/store-layout/store-footer/store-footer.component.html b/src/app/core/layout/store-layout/store-footer/store-footer.component.html index b672e3a..87bdd78 100644 --- a/src/app/core/layout/store-layout/store-footer/store-footer.component.html +++ b/src/app/core/layout/store-layout/store-footer/store-footer.component.html @@ -44,11 +44,14 @@
- @for (social of socialLinks; track social.label) { - - - {{ social.label }} - + @for (social of socialMedia; track social.code) { + }
diff --git a/src/app/core/layout/store-layout/store-footer/store-footer.component.ts b/src/app/core/layout/store-layout/store-footer/store-footer.component.ts index febc6eb..7a3e8a5 100644 --- a/src/app/core/layout/store-layout/store-footer/store-footer.component.ts +++ b/src/app/core/layout/store-layout/store-footer/store-footer.component.ts @@ -1,24 +1,21 @@ import { Component, Input } from '@angular/core'; +import { SocialMedia } from '../../../services/tenant.interface'; +import { SocialMediaComponent } from '../../../../shared/components/social-media/social-media.component'; export type StoreFooterSection = { heading: string; links: string[]; }; -export type StoreSocialLink = { - label: string; - iconClass: string; -}; - @Component({ selector: 'app-store-footer', - imports: [], + imports: [SocialMediaComponent], templateUrl: './store-footer.component.html', - styleUrl: './store-footer.component.scss' + styleUrl: './store-footer.component.scss', }) export class StoreFooterComponent { @Input({ required: true }) currentYear = new Date().getFullYear(); @Input({ required: true }) footerSections: StoreFooterSection[] = []; - @Input({ required: true }) socialLinks: StoreSocialLink[] = []; + @Input({ required: true }) socialMedia: SocialMedia[] = []; @Input() logoUrl: string | null = null; } diff --git a/src/app/core/layout/store-layout/store-layout.component.html b/src/app/core/layout/store-layout/store-layout.component.html index bb98274..6ce6352 100644 --- a/src/app/core/layout/store-layout/store-layout.component.html +++ b/src/app/core/layout/store-layout/store-layout.component.html @@ -38,7 +38,7 @@ diff --git a/src/app/core/layout/store-layout/store-layout.component.spec.ts b/src/app/core/layout/store-layout/store-layout.component.spec.ts index d1ecf0a..342833c 100644 --- a/src/app/core/layout/store-layout/store-layout.component.spec.ts +++ b/src/app/core/layout/store-layout/store-layout.component.spec.ts @@ -25,7 +25,33 @@ const tenant: Tenant = { header_bg_color: '#313131', footer_bg_color: '#313131', header_logo: 'https://example.com/header.png', - footer_logo: 'https://example.com/footer.png' + footer_logo: 'https://example.com/footer.png', + social_media: [ + { + code: 'instagram', + icon: 'instagram', + name: 'Instagram', + url: 'https://instagram.com/test', + }, + { + code: 'whatsapp', + icon: 'whatsapp', + name: 'WhatsApp', + url: 'https://wa.me/5493410000000', + }, + { + code: 'facebook', + icon: 'facebook', + name: 'Facebook', + url: 'https://facebook.com/test', + }, + { + code: 'linkedin', + icon: 'fa-brands fa-linkedin-in', + name: 'LinkedIn', + url: 'https://linkedin.com/company/test', + }, + ], }; describe('StoreLayoutComponent', () => { diff --git a/src/app/core/layout/store-layout/store-layout.component.ts b/src/app/core/layout/store-layout/store-layout.component.ts index aa41b4a..46c9706 100644 --- a/src/app/core/layout/store-layout/store-layout.component.ts +++ b/src/app/core/layout/store-layout/store-layout.component.ts @@ -2,11 +2,7 @@ import { Component, computed, inject, OnInit, signal } from '@angular/core'; import { Router, RouterOutlet } from '@angular/router'; import { TenantService } from '../../services/tenant.service'; import { CartService } from '../../services/cart/cart.service'; -import { - StoreFooterComponent, - StoreFooterSection, - StoreSocialLink, -} from './store-footer/store-footer.component'; +import { StoreFooterComponent, StoreFooterSection } from './store-footer/store-footer.component'; import { StoreHeaderComponent } from './store-header/store-header.component'; import { CartComponent, CartItemMock } from '../../../shared/components/cart/cart.component'; import { ButtonComponent } from '../../../shared/components/button/button.component'; @@ -122,22 +118,4 @@ export class StoreLayoutComponent implements OnInit { }, ]; - protected readonly socialLinks: StoreSocialLink[] = [ - { - label: 'Instagram', - iconClass: 'fa-brands fa-instagram', - }, - { - label: 'WhatsApp', - iconClass: 'fa-brands fa-whatsapp', - }, - { - label: 'Facebook', - iconClass: 'fa-brands fa-facebook', - }, - { - label: 'LinkedIn', - iconClass: 'fa-brands fa-linkedin-in', - }, - ]; } diff --git a/src/app/core/services/tenant.interface.ts b/src/app/core/services/tenant.interface.ts index 7be0295..6c7d3bb 100644 --- a/src/app/core/services/tenant.interface.ts +++ b/src/app/core/services/tenant.interface.ts @@ -29,6 +29,13 @@ export interface Menu { route: string; } +export interface SocialMedia { + code: string; + icon: string; + name: string; + url: string; +} + export interface Tenant { id: number; codigo: string; @@ -47,6 +54,7 @@ export interface Tenant { hero_config?: HeroConfig | null; event_config?: EventConfig | null; main_carousel_images?: string[]; + social_media?: SocialMedia[]; menues?: Menu[]; } diff --git a/src/app/shared/components/social-media/social-media.component.html b/src/app/shared/components/social-media/social-media.component.html new file mode 100644 index 0000000..1c28ae2 --- /dev/null +++ b/src/app/shared/components/social-media/social-media.component.html @@ -0,0 +1,12 @@ + + + {{ name() }} + diff --git a/src/app/shared/components/social-media/social-media.component.scss b/src/app/shared/components/social-media/social-media.component.scss new file mode 100644 index 0000000..2c3f6cb --- /dev/null +++ b/src/app/shared/components/social-media/social-media.component.scss @@ -0,0 +1,31 @@ +:host { + display: inline-flex; +} + +.social-media { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + border-radius: 50%; + color: inherit; + font-size: 1.25rem; + line-height: 1; + text-decoration: none; + transition: + color 0.15s ease-in-out, + background-color 0.15s ease-in-out, + transform 0.15s ease-in-out; + + &:hover { + color: #ffffff; + background-color: rgba(255, 255, 255, 0.12); + transform: translateY(-1px); + } + + &:focus-visible { + outline: 2px solid #ffffff; + outline-offset: 2px; + } +} diff --git a/src/app/shared/components/social-media/social-media.component.spec.ts b/src/app/shared/components/social-media/social-media.component.spec.ts new file mode 100644 index 0000000..697bea7 --- /dev/null +++ b/src/app/shared/components/social-media/social-media.component.spec.ts @@ -0,0 +1,40 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { SocialMediaComponent } from './social-media.component'; + +describe('SocialMediaComponent', () => { + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SocialMediaComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(SocialMediaComponent); + fixture.componentRef.setInput('code', 'instagram'); + fixture.componentRef.setInput('icon', 'instagram'); + fixture.componentRef.setInput('name', 'Instagram'); + fixture.componentRef.setInput('url', 'https://instagram.com/acme'); + fixture.detectChanges(); + }); + + it('renders a safe external social media link', () => { + const link = fixture.nativeElement.querySelector('a') as HTMLAnchorElement; + const icon = fixture.nativeElement.querySelector('i') as HTMLElement; + + expect(link.getAttribute('href')).toBe('https://instagram.com/acme'); + expect(link.getAttribute('target')).toBe('_blank'); + expect(link.getAttribute('rel')).toBe('noopener noreferrer'); + expect(link.getAttribute('aria-label')).toBe('Instagram'); + expect(link.dataset['socialMedia']).toBe('instagram'); + expect(icon.className).toBe('fa-brands fa-instagram'); + }); + + it('keeps a complete Font Awesome class', () => { + fixture.componentRef.setInput('icon', 'fa-brands fa-linkedin-in'); + fixture.detectChanges(); + + const icon = fixture.nativeElement.querySelector('i') as HTMLElement; + + expect(icon.className).toBe('fa-brands fa-linkedin-in'); + }); +}); diff --git a/src/app/shared/components/social-media/social-media.component.ts b/src/app/shared/components/social-media/social-media.component.ts new file mode 100644 index 0000000..dabb6ee --- /dev/null +++ b/src/app/shared/components/social-media/social-media.component.ts @@ -0,0 +1,21 @@ +import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core'; + +@Component({ + selector: 'app-social-media', + imports: [], + templateUrl: './social-media.component.html', + styleUrl: './social-media.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SocialMediaComponent { + readonly code = input.required(); + readonly icon = input.required(); + readonly name = input.required(); + readonly url = input.required(); + + protected readonly iconClass = computed(() => { + const icon = this.icon().trim(); + + return icon.includes('fa-') ? icon : `fa-brands fa-${icon}`; + }); +}