feat(store-layout): add dynamic background images to header and footer components

This commit is contained in:
2026-08-12 14:08:56 -03:00
parent 0b53253461
commit 12e2870d40
11 changed files with 57 additions and 9 deletions

View File

@@ -1,4 +1,7 @@
<footer class="store-layout__footer mt-auto text-light">
<footer
class="store-layout__footer mt-auto text-light"
[style.background-image]="backgroundImageUrl ? 'url(' + backgroundImageUrl + ')' : null"
>
<div class="container-xl px-3 px-md-4 py-4 py-md-5 store-layout__footer-content">
<div class="row g-4 store-layout__footer-grid">
<div class="col-12 store-layout__brand-column">

View File

@@ -1,5 +1,8 @@
.store-layout__footer {
background-color: var(--tenant-footer-bg);
background-repeat: no-repeat;
background-position: center;
background-size: cover;
}
.store-layout__mobile-divider {

View File

@@ -25,6 +25,7 @@ export class StoreFooterComponent {
@Input({ required: true }) footerSections: StoreFooterSection[] = [];
@Input({ required: true }) socialMedia: SocialMedia[] = [];
@Input() logoUrl: string | null = null;
@Input() backgroundImageUrl: string | null = null;
@Input() storeName: string | null = null;
readonly logoutClick = output<void>();
}

View File

@@ -1,4 +1,7 @@
<header class="store-layout__header">
<header
class="store-layout__header"
[style.background-image]="backgroundImageUrl() ? 'url(' + backgroundImageUrl() + ')' : null"
>
<div class="store-layout__header-main">
<div class="container-xl px-3 px-md-4 py-3 py-lg-4">
<div class="row align-items-center gy-3 gy-md-0">
@@ -74,12 +77,14 @@
</app-button>
}
<app-cart-icon
[quantity]="cartQuantity()"
ariaLabel="Carrito de compras"
title="Carrito"
(click)="cartClick.emit()"
/>
@if (displayCart()) {
<app-cart-icon
[quantity]="cartQuantity()"
ariaLabel="Carrito de compras"
title="Carrito"
(click)="cartClick.emit()"
/>
}
<div class="store-layout__user-menu d-none d-md-inline-flex position-relative">
<app-icon-button

View File

@@ -1,6 +1,9 @@
.store-layout__header {
color: #ffffff;
background-color: var(--tenant-header-bg);
background-repeat: no-repeat;
background-position: center;
background-size: cover;
}
.store-layout__header-main {

View File

@@ -29,6 +29,7 @@ export class StoreHeaderComponent {
private readonly router = inject(Router);
readonly logoUrl = input<string | null>(null);
readonly backgroundImageUrl = input<string | null>(null);
readonly cartQuantity = input<number>(0);
readonly user = input<AuthUser | null>(null);
readonly isAuthenticated = input<boolean>(false);
@@ -37,6 +38,7 @@ export class StoreHeaderComponent {
readonly categories = input<readonly Category[]>([]);
readonly displayCategories = input(true);
readonly displaySeachBar = input(true);
readonly displayCart = input(true);
readonly cartClick = output<void>();
readonly ticketsClick = output<void>();
readonly loginClick = output<void>();

View File

@@ -3,6 +3,7 @@
ngSkipHydration
[cartQuantity]="cartQuantity()"
[logoUrl]="tenant()?.header_logo ?? null"
[backgroundImageUrl]="tenant()?.header_bg_image ?? null"
[user]="user()"
[isAuthenticated]="isAuthenticated()"
[accountNavigationMenus]="accountNavigationMenus()"
@@ -10,6 +11,7 @@
[categories]="tenant()?.categories ?? []"
[displayCategories]="tenant()?.display_categories ?? true"
[displaySeachBar]="tenant()?.display_seach_bar ?? true"
[displayCart]="displayCart()"
(cartClick)="isCartOpen.set(!isCartOpen())"
(ticketsClick)="onTicketsClick()"
(loginClick)="onLoginClick()"
@@ -18,7 +20,7 @@
(categorySelect)="onCategorySelect($event)"
/>
@if (isCartOpen()) {
@if (displayCart() && isCartOpen()) {
<div class="store-layout__cart-overlay" (click)="isCartOpen.set(false)"></div>
<div class="store-layout__cart-dropdown card border-0 shadow-lg">
<app-cart
@@ -58,6 +60,7 @@
[footerSections]="footerSections()"
[socialMedia]="tenant()?.social_media ?? []"
[logoUrl]="tenant()?.footer_logo ?? null"
[backgroundImageUrl]="tenant()?.footer_bg_image ?? null"
[storeName]="tenant()?.nombre ?? null"
(logoutClick)="onLogoutClick()"
/>

View File

@@ -30,6 +30,8 @@ const tenant: Tenant = {
footer_bg_color: '#313131',
header_logo: 'https://example.com/header.png',
footer_logo: 'https://example.com/footer.png',
header_bg_image: 'https://example.com/header-background.png',
footer_bg_image: 'https://example.com/footer-background.png',
categories: [],
menues: [
{
@@ -210,6 +212,7 @@ describe('StoreLayoutComponent', () => {
categories: [{ id: 1, nombre: 'Remeras', subcategories: [] }],
display_categories: false,
display_seach_bar: false,
display_cart: false,
});
const fixture = TestBed.createComponent(StoreLayoutComponent);
@@ -219,6 +222,13 @@ describe('StoreLayoutComponent', () => {
expect(compiled.querySelector('#store-search-input')).toBeNull();
expect(compiled.querySelector('.store-layout__category-trigger')).toBeNull();
expect(compiled.querySelector('app-cart-icon')).toBeNull();
(fixture.componentInstance as any).isCartOpen.set(true);
fixture.detectChanges();
expect(compiled.querySelector('app-cart')).toBeNull();
expect(compiled.querySelector('.store-layout__cart-overlay')).toBeNull();
const header = fixture.debugElement.query(By.directive(StoreHeaderComponent));
(header.componentInstance as any).toggleMobileMenu();
@@ -245,6 +255,12 @@ describe('StoreLayoutComponent', () => {
`app-store-footer img.store-layout__brand-logo[src="${tenant.footer_logo}"]`,
),
).not.toBeNull();
expect(
(compiled.querySelector('.store-layout__header') as HTMLElement).style.backgroundImage,
).toContain(tenant.header_bg_image!);
expect(
(compiled.querySelector('.store-layout__footer') as HTMLElement).style.backgroundImage,
).toContain(tenant.footer_bg_image!);
expect(compiled.querySelector('.fa-cart-shopping')).not.toBeNull();
expect(compiled.querySelector('.fa-circle-user')).not.toBeNull();
expect(compiled.querySelector('.fa-instagram')).not.toBeNull();

View File

@@ -35,6 +35,7 @@ export class StoreLayoutComponent implements OnInit {
protected readonly isCartOpen = signal(false);
protected readonly isCreatingPurchase = signal(false);
protected readonly displayCart = computed(() => this.tenant()?.display_cart ?? true);
protected readonly cartSubtotal = computed(() => {
const cart = this.cartService.cart();

View File

@@ -104,6 +104,8 @@ export interface Tenant {
footer_bg_color: string;
header_logo: string;
footer_logo: string;
header_bg_image?: string | null;
footer_bg_image?: string | null;
website_type_code?: string | null;
website_type?: WebsiteType | null;
event_date_text?: string | null;
@@ -116,6 +118,7 @@ export interface Tenant {
search_items_per_page?: number;
display_categories?: boolean;
display_seach_bar?: boolean;
display_cart?: boolean;
social_media?: SocialMedia[];
menues?: Menu[];
categories: Category[];

View File

@@ -53,6 +53,14 @@
}
}
::ng-deep .hero-title h1 {
margin: 0;
color: inherit;
font-size: inherit;
font-weight: 700;
line-height: inherit;
}
::ng-deep .hero-description,
.hero-description {
color: #666666;