Implement experimenta product listing page and enhance navigation components

- Added a new page for displaying experimenta annual passes, integrating ProductCard and ProductGrid components for product presentation.
- Updated API to filter products by category, allowing for specific product queries.
- Enhanced navigation components, including AreaTabs and RoleSwitcher, to improve user experience and accessibility.
- Refactored existing components for better styling consistency and responsiveness.
- Improved dropdown menu components with updated styles and hover effects for better usability.
This commit is contained in:
Bastian Masanek
2025-11-01 20:08:28 +01:00
parent 81495d5e17
commit a826ea1344
9 changed files with 207 additions and 64 deletions

View File

@@ -0,0 +1,142 @@
<script setup lang="ts">
/**
* experimenta Products Listing Page
*
* Displays experimenta annual passes using ProductCard and ProductGrid components.
* Fetches only experimenta category products from the database via API.
*/
// Page metadata
definePageMeta({
layout: 'default',
})
// Type definition for product
interface Product {
id: string
navProductId: string
name: string
description: string
price: string
stockQuantity: number
category: string
active: boolean
createdAt: Date
updatedAt: Date
}
// Fetch products from API - only experimenta passes
const { data: products, error, pending } = await useFetch<Product[]>('/api/products', {
query: {
category: 'annual-pass',
},
})
// Map product categories to badges
const getBadge = (category: string): string | undefined => {
const badges: Record<string, string> = {
'annual-pass': 'Beliebt',
}
return badges[category]
}
// Map product categories to discount percentages (for demo)
const getDiscount = (category: string): number | undefined => {
const discounts: Record<string, number> = {
// No discounts for now
}
return discounts[category]
}
</script>
<template>
<NuxtLayout name="default">
<div class="min-h-screen bg-gradient-primary px-4 py-12 md:px-6 lg:px-8">
<!-- Page Header -->
<div class="mx-auto mb-12 max-w-container-wide text-center">
<h1 class="mb-4 text-4xl font-bold text-white md:text-5xl">
experimenta Jahreskarten
</h1>
<p class="mx-auto max-w-2xl text-lg text-white/80">
Erlebe die Ausstellungswelt der experimenta ein ganzes Jahr lang.
Mit freiem Eintritt zu allen Ausstellungen, Science Dome Shows und Sonderausstellungen.
</p>
</div>
<!-- Loading State -->
<div v-if="pending" class="mx-auto max-w-container-wide text-center">
<div class="card-glass inline-block">
<p class="text-white/80">Produkte werden geladen...</p>
</div>
</div>
<!-- Error State -->
<div v-else-if="error" class="mx-auto max-w-container-wide">
<div class="card-glass border-red/50 bg-red/10">
<h2 class="mb-2 text-xl font-semibold text-red">
Fehler beim Laden der Produkte
</h2>
<p class="text-white/80">
{{ error.message || 'Ein unbekannter Fehler ist aufgetreten.' }}
</p>
</div>
</div>
<!-- Products Grid -->
<div v-else-if="products && products.length > 0" class="mx-auto max-w-container-wide">
<ProductGrid :columns="3">
<ProductCard
v-for="product in products"
:key="product.id"
image="/img/makerspace-jk-2025.jpg"
:title="product.name"
:description="product.description"
:price="Number(product.price)"
:badge="getBadge(product.category)"
:discount-percentage="getDiscount(product.category)"
:product-id="product.id"
/>
</ProductGrid>
</div>
<!-- Empty State -->
<div v-else class="mx-auto max-w-container-wide text-center">
<div class="card-glass">
<h2 class="mb-2 text-xl font-semibold text-white">
Keine Produkte verfügbar
</h2>
<p class="text-white/80">
Aktuell sind keine Produkte verfügbar. Bitte schaue später noch einmal vorbei.
</p>
</div>
</div>
<!-- Info Card -->
<div class="mx-auto mt-12 max-w-container-wide">
<div class="card-info card-accent-border">
<h2 class="mb-3 text-xl font-semibold text-experimenta-accent">
Warum eine experimenta Jahreskarte?
</h2>
<ul class="space-y-2 text-white/90">
<li class="flex items-start gap-2">
<span class="mt-1 text-experimenta-accent"></span>
<span>365 Tage unbegrenzter Zugang zur Ausstellungswelt</span>
</li>
<li class="flex items-start gap-2">
<span class="mt-1 text-experimenta-accent"></span>
<span>Freier Eintritt zu allen Science Dome Shows</span>
</li>
<li class="flex items-start gap-2">
<span class="mt-1 text-experimenta-accent"></span>
<span>Zugang zu wechselnden Sonderausstellungen</span>
</li>
<li class="flex items-start gap-2">
<span class="mt-1 text-experimenta-accent"></span>
<span>Flexible Nutzung komme so oft du möchtest</span>
</li>
</ul>
</div>
</div>
</div>
</NuxtLayout>
</template>

View File

@@ -25,8 +25,12 @@ interface Product {
updatedAt: Date
}
// Fetch products from API
const { data: products, error, pending } = await useFetch<Product[]>('/api/products')
// Fetch products from API - only Makerspace and Educator passes
const { data: products, error, pending } = await useFetch<Product[]>('/api/products', {
query: {
category: 'makerspace-annual-pass,educator-annual-pass',
},
})
// Map product categories to badges
const getBadge = (category: string): string | undefined => {
@@ -52,11 +56,11 @@ const getDiscount = (category: string): number | undefined => {
<!-- Page Header -->
<div class="mx-auto mb-12 max-w-container-wide text-center">
<h1 class="mb-4 text-4xl font-bold text-white md:text-5xl">
Unsere Jahreskarten
Makerspace Jahreskarten
</h1>
<p class="mx-auto max-w-2xl text-lg text-white/80">
Wähle die passende Jahreskarte für deine Bedürfnisse. Alle Karten sind 365 Tage gültig
und sofort einsetzbar.
Dein Zugang zum Makerspace. Nutze modernste Werkzeuge, 3D-Drucker, Lasercutter und vieles mehr.
Alle Karten sind 365 Tage gültig und sofort einsetzbar.
</p>
</div>