Browse Source

Add educator annual pass functionality and update product listings

- Introduced a new page for educator annual passes, displaying relevant products with a dedicated layout and loading/error states.
- Updated AreaTabs component to include the educator tab and adjusted routing logic.
- Modified useAuth to redirect users to the products page after login.
- Adjusted mock product prices and stock quantities in the database seeding script to reflect new pricing strategy.

These changes enhance the user experience for educators and improve product visibility in the application.
main
Bastian Masanek 2 months ago
parent
commit
e252d68f0c
  1. 31
      app/components/navigation/AreaTabs.vue
  2. 4
      app/composables/useAuth.ts
  3. 142
      app/pages/educator/index.vue
  4. 10
      server/database/seed.ts

31
app/components/navigation/AreaTabs.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import { Wrench, FlaskConical, Ticket, Sparkles } from 'lucide-vue-next'
import { Wrench, FlaskConical, Ticket, Sparkles, GraduationCap } from 'lucide-vue-next'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Badge } from '@/components/ui/badge'
@ -14,14 +14,6 @@ interface ProductArea {
}
const areas: ProductArea[] = [
{
id: 'experimenta',
label: 'experimenta Jahreskarte',
icon: Sparkles,
enabled: true,
visible: true,
route: '/experimenta',
},
{
id: 'makerspace',
label: 'Makerspace Jahreskarte',
@ -30,12 +22,29 @@ const areas: ProductArea[] = [
visible: true,
route: '/products',
},
{
id: 'educator',
label: 'Pädagogen Jahreskarte',
icon: GraduationCap,
enabled: true,
visible: true,
route: '/educator',
},
{
id: 'experimenta',
label: 'experimenta Jahreskarte',
icon: Sparkles,
enabled: true,
visible: true,
badge: 'Demnächst',
route: '/experimenta',
},
{
id: 'labs',
label: 'Labore',
icon: FlaskConical,
enabled: false,
visible: true,
visible: false,
badge: 'Demnächst',
route: '/labs',
},
@ -47,6 +56,8 @@ const currentArea = computed(() => {
// Determine current area based on route
if (route.path.startsWith('/products') || route.path === '/') {
return 'makerspace'
} else if (route.path.startsWith('/educator')) {
return 'educator'
} else if (route.path.startsWith('/labs')) {
return 'labs'
} else if (route.path.startsWith('/experimenta')) {

4
app/composables/useAuth.ts

@ -37,9 +37,9 @@ export function useAuth() {
// Refresh user session
await fetch()
// Redirect to homepage or saved destination
// Redirect to products page or saved destination
const redirectAfterLogin = useCookie('redirect_after_login')
const destination = redirectAfterLogin.value || '/'
const destination = redirectAfterLogin.value || '/products'
redirectAfterLogin.value = null // Clear cookie
navigateTo(destination)

142
app/pages/educator/index.vue

@ -0,0 +1,142 @@
<script setup lang="ts">
/**
* Educator Products Listing Page
*
* Displays educator annual passes using ProductCard and ProductGrid components.
* Fetches only educator 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 educator passes
const { data: products, error, pending } = await useFetch<Product[]>('/api/products', {
query: {
category: 'educator-annual-pass',
},
})
// Map product categories to badges
const getBadge = (category: string): string | undefined => {
const badges: Record<string, string> = {
'educator-annual-pass': 'Für Pädagogen',
}
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">
Pädagogische Jahreskarten
</h1>
<p class="mx-auto max-w-2xl text-lg text-white/80">
Spezielle Jahreskarten für Pädagogen und Lehrkräfte.
Nutze die experimenta für deine Bildungsarbeit und profitiere von vergünstigten Konditionen.
</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 Pädagogen-Jahreskarten 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 Pädagogen-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>Speziell für Lehrkräfte und Pädagogen entwickelt</span>
</li>
<li class="flex items-start gap-2">
<span class="mt-1 text-experimenta-accent"></span>
<span>Vergünstigte Konditionen für Bildungseinrichtungen</span>
</li>
<li class="flex items-start gap-2">
<span class="mt-1 text-experimenta-accent"></span>
<span>Ideal für Schulausflüge und Projektwochen</span>
</li>
<li class="flex items-start gap-2">
<span class="mt-1 text-experimenta-accent"></span>
<span>Flexible Nutzung für deine Bildungsarbeit</span>
</li>
</ul>
</div>
</div>
</div>
</NuxtLayout>
</template>

10
server/database/seed.ts

@ -62,7 +62,7 @@ const mockProducts: Array<{
name: 'Makerspace Jahreskarte',
description:
'Unbegrenzter Zugang zum Makerspace für 365 Tage. Nutze modernste Werkzeuge, 3D-Drucker, Lasercutter und vieles mehr. Perfekt für Maker, Tüftler und kreative Köpfe.',
price: '120.00',
price: '30.00',
stockQuantity: 100,
category: 'makerspace-annual-pass',
active: true,
@ -73,7 +73,7 @@ const mockProducts: Array<{
name: 'experimenta Jahreskarte',
description:
'Erlebe die Ausstellungswelt der experimenta ein ganzes Jahr lang. Mit freiem Eintritt zu allen Ausstellungen, Science Dome Shows und Sonderausstellungen.',
price: '85.00',
price: '49.00',
stockQuantity: 200,
category: 'annual-pass',
active: true,
@ -84,11 +84,11 @@ const mockProducts: Array<{
name: 'Pädagogen Jahreskarte',
description:
'Speziell für Lehrkräfte und Pädagogen. Mit exklusiven Fortbildungsangeboten, didaktischen Materialien und freiem Zugang zu allen Ausstellungen.',
price: '60.00',
stockQuantity: 50,
price: '0.00',
stockQuantity: 99999,
category: 'educator-annual-pass',
active: true,
roles: ['educator'],
roles: ['private', 'educator'],
},
]

Loading…
Cancel
Save