Some checks failed
Build and publish / build (push) Failing after 19s
Source moved verbatim from mp/shop/ on 2026-04-29; mp was the first concrete adapter consuming the libreshop toolkit. Builds and publishes git.librete.ch/libreshop/shop on every main / v* push via the standard .gitea/workflows/build.yml shared across libreshop components.
99 lines
3.6 KiB
Vue
99 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>
|