Some checks failed
Build and Deploy / test-backend (push) Successful in 39s
Build and Deploy / test-frontend (push) Successful in 1m3s
Build and Deploy / build-and-push (push) Successful in 32s
Build and Deploy / deploy (push) Successful in 19s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / prettier-autofix (pull_request) Failing after 11s
PR Checks / test-backend (pull_request) Successful in 28s
PR Checks / test-frontend (pull_request) Successful in 1m3s
236 lines
7.7 KiB
HTML
236 lines
7.7 KiB
HTML
<section class="shop-page">
|
|
<div class="container ui-simple-hero shop-hero">
|
|
<h1 class="ui-simple-hero__title">{{ "NAV.SHOP" | translate }}</h1>
|
|
<p class="ui-simple-hero__subtitle">{{ heroSubtitle() }}</p>
|
|
</div>
|
|
|
|
<div class="container shop-layout">
|
|
<aside class="shop-sidebar">
|
|
<app-card class="category-card">
|
|
<div class="panel-head">
|
|
<div>
|
|
<p class="panel-kicker">
|
|
{{ "SHOP.CATEGORY_PANEL_KICKER" | translate }}
|
|
</p>
|
|
<h2 class="panel-title">
|
|
{{ "SHOP.CATEGORY_PANEL_TITLE" | translate }}
|
|
</h2>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
class="category-link"
|
|
[class.active]="!currentCategorySlug()"
|
|
(click)="navigateToCategory()"
|
|
>
|
|
<span class="category-name">{{
|
|
"SHOP.ALL_CATEGORIES" | translate
|
|
}}</span>
|
|
</button>
|
|
|
|
<div class="category-list">
|
|
@for (node of categoryNodes(); track trackByCategory($index, node)) {
|
|
<button
|
|
type="button"
|
|
class="category-link"
|
|
[class.active]="node.current"
|
|
[style.--depth]="node.depth"
|
|
(click)="navigateToCategory(node.slug)"
|
|
>
|
|
<span class="category-name">{{ node.name }}</span>
|
|
<small class="category-count">{{ node.productCount }}</small>
|
|
</button>
|
|
}
|
|
</div>
|
|
</app-card>
|
|
|
|
<app-card class="cart-card">
|
|
<div class="panel-head">
|
|
<div>
|
|
<p class="panel-kicker">{{ "SHOP.CART_TITLE" | translate }}</p>
|
|
<h2 class="panel-title">
|
|
{{ "SHOP.CART_SUMMARY_TITLE" | translate }}
|
|
</h2>
|
|
</div>
|
|
@if (cartHasItems()) {
|
|
<button
|
|
type="button"
|
|
class="text-action"
|
|
[disabled]="cartMutating()"
|
|
(click)="clearCart()"
|
|
>
|
|
{{ "SHOP.CLEAR_CART" | translate }}
|
|
</button>
|
|
}
|
|
</div>
|
|
|
|
@if (cartLoading() && !cart()) {
|
|
<p class="panel-empty">{{ "SHOP.CART_LOADING" | translate }}</p>
|
|
} @else if (!cartHasItems()) {
|
|
<p class="panel-empty">{{ "SHOP.CART_EMPTY" | translate }}</p>
|
|
} @else {
|
|
<div class="cart-lines">
|
|
@for (item of cartItems(); track trackByCartItem($index, item)) {
|
|
<article class="cart-line">
|
|
<div class="cart-line-copy">
|
|
<strong>{{ cartItemName(item) }}</strong>
|
|
@if (cartItemVariant(item); as variant) {
|
|
<span class="cart-line-meta">{{
|
|
variant | translate
|
|
}}</span>
|
|
}
|
|
@if (cartItemColor(item); as color) {
|
|
<span class="cart-line-color">
|
|
<span
|
|
class="color-dot"
|
|
[style.background-color]="cartItemColorHex(item)"
|
|
></span>
|
|
<span>{{ color | translate }}</span>
|
|
</span>
|
|
}
|
|
</div>
|
|
|
|
<div class="cart-line-controls">
|
|
<div class="qty-control">
|
|
<button
|
|
type="button"
|
|
[disabled]="
|
|
cartMutating() && busyLineItemId() === item.id
|
|
"
|
|
(click)="decreaseQuantity(item)"
|
|
>
|
|
-
|
|
</button>
|
|
<span>{{ item.quantity }}</span>
|
|
<button
|
|
type="button"
|
|
[disabled]="
|
|
cartMutating() && busyLineItemId() === item.id
|
|
"
|
|
(click)="increaseQuantity(item)"
|
|
>
|
|
+
|
|
</button>
|
|
</div>
|
|
<strong class="line-total">{{
|
|
item.unitPriceChf * item.quantity | currency: "CHF"
|
|
}}</strong>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
class="line-remove"
|
|
[disabled]="cartMutating() && busyLineItemId() === item.id"
|
|
(click)="removeItem(item)"
|
|
>
|
|
{{ "SHOP.REMOVE" | translate }}
|
|
</button>
|
|
</article>
|
|
}
|
|
</div>
|
|
|
|
<div class="cart-totals">
|
|
<div class="cart-total-row">
|
|
<span>{{ "SHOP.CART_SUBTOTAL" | translate }}</span>
|
|
<strong>{{
|
|
cart()?.itemsTotalChf || 0 | currency: "CHF"
|
|
}}</strong>
|
|
</div>
|
|
<div class="cart-total-row">
|
|
<span>{{ "SHOP.CART_SHIPPING" | translate }}</span>
|
|
<strong>{{
|
|
cart()?.shippingCostChf || 0 | currency: "CHF"
|
|
}}</strong>
|
|
</div>
|
|
<div class="cart-total-row cart-total-row-final">
|
|
<span>{{ "SHOP.CART_TOTAL" | translate }}</span>
|
|
<strong>{{
|
|
cart()?.grandTotalChf || 0 | currency: "CHF"
|
|
}}</strong>
|
|
</div>
|
|
</div>
|
|
|
|
<app-button
|
|
variant="primary"
|
|
[fullWidth]="true"
|
|
[disabled]="cartMutating()"
|
|
(click)="goToCheckout()"
|
|
>
|
|
{{ "SHOP.GO_TO_CHECKOUT" | translate }}
|
|
</app-button>
|
|
}
|
|
</app-card>
|
|
</aside>
|
|
|
|
<section class="catalog-content">
|
|
@if (error()) {
|
|
<div class="catalog-state catalog-state-error">
|
|
{{ error() | translate }}
|
|
</div>
|
|
} @else {
|
|
<section class="catalog-panel">
|
|
<div class="section-head catalog-head">
|
|
<div>
|
|
<p class="ui-eyebrow ui-eyebrow--compact">
|
|
{{ catalogEyebrow() }}
|
|
</p>
|
|
<h2 class="section-title">{{ catalogTitle() }}</h2>
|
|
</div>
|
|
<span class="catalog-counter">
|
|
{{ products().length }}
|
|
{{ "SHOP.ITEMS_FOUND" | translate }}
|
|
</span>
|
|
</div>
|
|
|
|
@if (loading() || softFallbackActive()) {
|
|
<div class="product-grid skeleton-grid">
|
|
@for (ghost of [1, 2, 3, 4]; track ghost) {
|
|
<div class="skeleton-card"></div>
|
|
}
|
|
</div>
|
|
} @else if (products().length === 0) {
|
|
<div class="catalog-state">
|
|
{{ "SHOP.EMPTY_CATEGORY" | translate }}
|
|
</div>
|
|
} @else {
|
|
<div class="product-grid">
|
|
@for (
|
|
product of products();
|
|
track trackByProduct($index, product)
|
|
) {
|
|
<app-product-card
|
|
[product]="product"
|
|
[cartQuantity]="productCartQuantity(product.id)"
|
|
></app-product-card>
|
|
}
|
|
</div>
|
|
}
|
|
</section>
|
|
}
|
|
</section>
|
|
</div>
|
|
|
|
<section class="container shop-custom-cta">
|
|
<app-card class="shop-custom-cta-card">
|
|
<div class="shop-custom-cta-inner">
|
|
<div class="shop-custom-cta-copy">
|
|
<p class="panel-kicker">
|
|
{{ "SHOP.CUSTOM_PART_FOOTER_TITLE" | translate }}
|
|
</p>
|
|
<h2 class="shop-custom-cta-title">
|
|
{{ "SHOP.CUSTOM_PART_FOOTER_TEXT" | translate }}
|
|
</h2>
|
|
</div>
|
|
|
|
<app-button
|
|
variant="primary"
|
|
[routerLink]="languageService.localizedPath('/contact')"
|
|
>
|
|
{{ "NAV.CONTACT" | translate }}
|
|
</app-button>
|
|
</div>
|
|
</app-card>
|
|
</section>
|
|
</section>
|