Files
shop/pages/notebooks/index.vue
Michael Czechowski 44107c0734
Some checks failed
Build and publish / build (push) Failing after 19s
feat: extract shop from mp/shop — initial libreshop/shop
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.
2026-04-29 17:48:56 +02:00

177 lines
5.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<main>
<!-- Hero Section -->
<section class="bg-gray-900 text-white py-16 lg:py-24">
<div class="container mx-auto px-6 text-center">
<p class="text-sm uppercase tracking-widest opacity-60 mb-4">Unsere Kollektion</p>
<h1 class="text-4xl lg:text-5xl font-bebas mb-4">Alle Notizbücher</h1>
<p class="text-lg opacity-80 max-w-2xl mx-auto">
Handgefertigte Notizbücher aus Stuttgart mit 100% Recyclingpapier und traditioneller Fadenheftung.
</p>
</div>
</section>
<!-- Category Navigation -->
<section class="py-8 border-b border-gray-200">
<div class="container mx-auto px-6">
<div class="flex flex-wrap justify-center gap-3">
<NuxtLink
to="/notebooks"
class="px-4 py-2 rounded-full text-sm font-medium transition-colors bg-gray-900 text-white"
>
Alle
</NuxtLink>
<NuxtLink
v-for="cover in covers"
:key="cover.id"
:to="`/notebooks/${cover.slug}`"
class="px-4 py-2 rounded-full text-sm font-medium transition-colors bg-gray-100 text-gray-700 hover:bg-gray-200"
>
{{ cover.name }}
</NuxtLink>
</div>
</div>
</section>
<!-- Products Grid -->
<section class="py-12 lg:py-16">
<div class="container mx-auto px-6">
<!-- Section Header -->
<div class="flex justify-between items-center mb-8">
<div>
<h2 class="text-xl font-semibold">{{ pagination?.total ?? 0 }} Notizbücher</h2>
<p class="text-sm text-gray-500">Seite {{ currentPage }} von {{ pagination?.pageCount ?? 1 }}</p>
</div>
</div>
<!-- Loading State -->
<div v-if="pending" class="text-center py-12">
<LoadingSpinner />
<p class="mt-4 text-gray-500">Produkte werden geladen...</p>
</div>
<!-- Error State -->
<div v-else-if="error" class="text-center py-12 text-red-600">
<p>Fehler beim Laden der Produkte</p>
</div>
<!-- Products Grid -->
<div v-else class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-6">
<ProductCard v-for="product in products" :key="product.id" :product="normalizeProduct(product)" />
</div>
<!-- Pagination -->
<div v-if="pagination && pagination.pageCount > 1" class="mt-12 flex justify-center gap-2">
<NuxtLink
v-for="pageNum in pagination.pageCount"
:key="pageNum"
:to="pageNum === 1 ? '/notebooks' : `/notebooks?page=${pageNum}`"
class="px-4 py-2 rounded-full text-sm font-medium transition-colors"
:class="pageNum === currentPage ? 'bg-gray-900 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
>
{{ pageNum }}
</NuxtLink>
</div>
</div>
</section>
<!-- Footer CTA -->
<section class="py-16 bg-gray-50 border-t border-gray-200">
<div class="container mx-auto px-6 text-center">
<h3 class="text-2xl font-bold mb-3">Fragen zu unseren Produkten?</h3>
<p class="text-gray-600 max-w-xl mx-auto mb-6">
Jedes Notizbuch wird in unserer Stuttgarter Werkstatt von Hand gebunden.
</p>
<NuxtLink
to="/about"
class="inline-block px-6 py-3 rounded-full font-medium bg-gray-900 text-white hover:bg-gray-700 transition-colors"
>
Mehr über uns
</NuxtLink>
</div>
</section>
</main>
</template>
<script setup lang="ts">
import type { Product } from "~/types";
import { slugify } from "~/utils/slugify";
import { trackEvent } from "~/utils/trackEvent";
const route = useRoute();
const currentPage = computed(() => parseInt(route.query.page as string) || 1);
const shopApi = useShopApi();
// Fetch products (cheapest variant per cover+pattern)
const { data, pending, error } = await useAsyncData(
() => `products-all-page-${currentPage.value}`,
() => shopApi.getCheapestProducts(undefined, currentPage.value, 20),
{ watch: [currentPage] }
);
// Fetch covers for category navigation
const { data: coversData } = await useAsyncData("covers-nav", async () => {
try {
const { data } = await shopApi.getProductCovers();
return data
.map((cover: any) => ({
id: cover.id,
name: cover.attributes?.name,
slug: slugify(cover.attributes?.name ?? ""),
sort: cover.attributes?.sort ?? 0
}))
.sort((a: any, b: any) => a.sort - b.sort);
} catch {
return [];
}
});
const covers = computed(() => coversData.value ?? []);
const products = computed(() => data.value?.data ?? []);
const pagination = computed(() => data.value?.meta?.pagination);
// Normalize Strapi response to Product type
function normalizeProduct(item: any): Product {
const attrs = item.attributes ?? item;
return {
id: item.id,
name: attrs.name,
slug: attrs.slug,
totalProductPrice: attrs.totalProductPrice ?? attrs.price,
pattern: attrs.pattern?.data ? { id: attrs.pattern.data.id, name: attrs.pattern.data.attributes?.name } : attrs.pattern,
cover: attrs.cover?.data ? { id: attrs.cover.data.id, name: attrs.cover.data.attributes?.name } : attrs.cover,
ruling: attrs.ruling?.data ? { id: attrs.ruling.data.id, name: attrs.ruling.data.attributes?.name } : attrs.ruling,
pages: attrs.pages?.data ? { id: attrs.pages.data.id, name: attrs.pages.data.attributes?.name } : attrs.pages,
images: attrs.images
};
}
// SEO
useSeoMeta({
title: "Alle Notizbücher | MUELLERPRINTS",
description: "Entdecken Sie unsere handgefertigten Notizbücher aus Stuttgart mit 100% Recyclingpapier.",
ogTitle: "Alle Notizbücher | MUELLERPRINTS",
ogDescription: "Entdecken Sie unsere handgefertigten Notizbücher aus Stuttgart mit 100% Recyclingpapier."
});
// Track category view on mount
onMounted(() => {
trackEvent("category-viewed", {
category: "all",
page: currentPage.value,
totalProducts: pagination.value?.total ?? 0
});
});
// Track pagination clicks
watch(currentPage, (newPage, oldPage) => {
if (newPage !== oldPage) {
trackEvent("category-pagination-clicked", {
category: "all",
page: newPage
});
}
});
</script>