build / build (push) Successful in 1m38s
Recovered work in progress that sat uncommitted since 2026-05: a SelectionBox renders as a plain div instead of a NuxtLink when locked, so a variant with only one choice reads as decided rather than clickable, and it reports aria-checked in that state. useProductContent gained the content lookups the details and index pages now use, the cookie banner says Schließen instead of Akzeptieren, the Über uns header entry is gone, and ProductCard's hover shadow no longer relies on a short-circuit that could yield a non-array.
97 lines
3.6 KiB
Vue
97 lines
3.6 KiB
Vue
<template>
|
|
<a
|
|
:href="product.slug ? `/details/${product.slug}` : undefined"
|
|
class="cursor-pointer w-full gap-2 flex flex-col items-stretch group hover:scale-105 transition-all z-100"
|
|
@click="handleProductClick"
|
|
>
|
|
<div
|
|
v-if="variant === 'neutral'"
|
|
class="p-12 flex items-center justify-center rounded-lg relative"
|
|
>
|
|
<div class="absolute bottom-5 drop-shadow-sm text-gray-800 text-md">{{ product.pattern?.name }}</div>
|
|
<img
|
|
v-if="product.images?.images"
|
|
:src="product.images?.images[0].formats.small.url"
|
|
:alt="product.name"
|
|
class="lg:w-full object-cover max-h-64 lg:max-h-fit" loading="lazy"
|
|
/>
|
|
<div v-else class="bg-black opacity-5 shadow-sm lg:w-full object-cover min-h-48 lg:max-h-fit"></div>
|
|
</div>
|
|
<Background
|
|
v-else
|
|
:coverId="product.cover?.id"
|
|
:patternId="product.pattern?.id"
|
|
:shade="200"
|
|
:gradient="true"
|
|
:class="[...shadowColors]"
|
|
class="p-12 flex items-center justify-center rounded-lg shadow-none group-hover:shadow-2xl transition-all duration-300 relative"
|
|
>
|
|
<div class="absolute bottom-5 drop-shadow-sm text-gray-800 text-md">{{ product.pattern?.name }}</div>
|
|
<img
|
|
v-if="product.images?.images"
|
|
:src="product.images?.images[0].formats.small.url"
|
|
:alt="product.name"
|
|
class="lg:w-full object-cover max-h-64 lg:max-h-fit" loading="lazy"
|
|
/>
|
|
<div v-else class="bg-black opacity-5 shadow-sm lg:w-full object-cover min-h-48 lg:max-h-fit"></div>
|
|
</Background>
|
|
<div class="flex flex-col justify-between">
|
|
<!-- Product Options Pills -->
|
|
<div v-if="hasProductOptions" class="pt-4 pb-1 flex flex-wrap gap-1">
|
|
<span v-if="product.cover?.name" class="text-xs px-2 py-1 bg-gray-100 rounded-full text-gray-600">{{ product.cover.name }}</span>
|
|
<span v-if="product.ruling?.name" class="text-xs px-2 py-1 bg-gray-100 rounded-full text-gray-600">{{ product.ruling.name }}</span>
|
|
<span v-if="product.pages?.name" class="text-xs px-2 py-1 bg-gray-100 rounded-full text-gray-600">{{ product.pages.name }}</span>
|
|
</div>
|
|
<!-- Price -->
|
|
<div v-if="product.totalProductPrice" class="px-2 py-2">
|
|
<p class="text-gray-600 text-xs text-left">ab {{ numberFormatter(product.totalProductPrice, "€") }}</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { randomTailwindColor } from "~/utils/randomTailwindColor";
|
|
import { numberFormatter } from "~/utils/numberFormatter";
|
|
import { trackEvent } from "~/utils/trackEvent";
|
|
import type { Product } from "~/types";
|
|
|
|
const props = withDefaults(defineProps<{
|
|
product: Product;
|
|
variant?: "default" | "neutral";
|
|
}>(), {
|
|
variant: "default",
|
|
});
|
|
|
|
const shadowColors = computed(() => {
|
|
return props.product?.pattern
|
|
? [randomTailwindColor(props.product.pattern.id, "group-hover:shadow", 200, "/60")]
|
|
: [];
|
|
});
|
|
|
|
const hasProductOptions = computed(() => {
|
|
return props.product?.cover || props.product?.ruling || props.product?.pages;
|
|
});
|
|
|
|
function handlePersonalizedProductClick() {
|
|
const subject = `Anfrage für personalisiertes Produkt: ${props.product.cover?.name}`;
|
|
const body = `Hallo,\n\nich interessiere mich für ein personalisiertes Produkt: ${props.product.cover?.name}\n\nBitte kontaktieren Sie mich für weitere Details.\n\nVielen Dank!`;
|
|
window.location.href = `mailto:paperwork@muellerprints.de?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
|
|
}
|
|
|
|
function handleProductClick() {
|
|
trackEvent("product-card-clicked", {
|
|
productId: props.product.id,
|
|
productSlug: props.product.slug,
|
|
productName: props.product.name,
|
|
price: props.product.totalProductPrice
|
|
});
|
|
|
|
if (props.product.slug) {
|
|
navigateTo(`/details/${props.product.slug}`);
|
|
} else {
|
|
handlePersonalizedProductClick();
|
|
}
|
|
}
|
|
</script>
|