Enhance CheckoutForm and Order components with user data integration

- Refactored CheckoutForm.vue to utilize an extended user type, incorporating additional address fields for improved user data handling.
- Updated OrderSummary.vue to conditionally display salutation based on user input.
- Standardized error alert styling across multiple pages, changing variant from 'destructive' to 'error' for consistency.
- Adjusted button styles in various components to align with the new 'experimenta' variant.

These changes aim to improve user experience by ensuring accurate data representation and consistent UI elements across the checkout and order processes.
This commit is contained in:
Bastian Masanek
2025-11-03 17:00:22 +01:00
parent 66ba95439d
commit 782bd6cdd7
8 changed files with 105 additions and 49 deletions

View File

@@ -64,7 +64,7 @@ interface Props {
loading?: boolean
}
const props = defineProps<Props>()
defineProps<Props>()
const emit = defineEmits<{
submit: [data: z.infer<typeof checkoutSchema>]
@@ -72,20 +72,36 @@ const emit = defineEmits<{
const { user } = useAuth()
// Extended user type with address fields
type ExtendedUser = typeof user.value & {
id?: string
firstName?: string
lastName?: string
email?: string
salutation?: 'male' | 'female' | 'other' | null
dateOfBirth?: Date | string | null
street?: string | null
postCode?: string | null
city?: string | null
countryCode?: string | null
}
const extendedUser = user.value as ExtendedUser
// Form state
const form = reactive({
salutation: (user.value?.salutation as 'male' | 'female' | 'other') || 'other',
firstName: user.value?.firstName || '',
lastName: user.value?.lastName || '',
dateOfBirth: user.value?.dateOfBirth
? new Date(user.value.dateOfBirth).toISOString().split('T')[0]
salutation: (extendedUser?.salutation as 'male' | 'female' | 'other') || 'other',
firstName: extendedUser?.firstName || '',
lastName: extendedUser?.lastName || '',
dateOfBirth: extendedUser?.dateOfBirth
? new Date(extendedUser.dateOfBirth).toISOString().split('T')[0]
: '',
street: user.value?.street || '',
postCode: user.value?.postCode || '',
city: user.value?.city || '',
countryCode: user.value?.countryCode || 'DE',
street: extendedUser?.street || '',
postCode: extendedUser?.postCode || '',
city: extendedUser?.city || '',
countryCode: extendedUser?.countryCode || 'DE',
// Pre-checked if user doesn't have address yet
saveAddress: !user.value?.street,
saveAddress: !extendedUser?.street,
})
// Validation errors
@@ -131,7 +147,7 @@ function getError(field: string): string {
<div class="space-y-2">
<label class="text-sm font-medium text-white">Anrede *</label>
<Select v-model="form.salutation" :disabled="loading">
<SelectTrigger :class="{ 'border-red-500': hasError('salutation') }">
<SelectTrigger :class="{ 'border-warning/50': hasError('salutation') }">
<SelectValue placeholder="Bitte wählen" />
</SelectTrigger>
<SelectContent>
@@ -140,7 +156,8 @@ function getError(field: string): string {
<SelectItem value="other">Keine Angabe</SelectItem>
</SelectContent>
</Select>
<p v-if="hasError('salutation')" class="text-sm text-red-400">
<p v-if="hasError('salutation')" class="form-error">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-warning flex-shrink-0"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
{{ getError('salutation') }}
</p>
</div>
@@ -154,9 +171,10 @@ function getError(field: string): string {
type="text"
placeholder="Max"
:disabled="loading"
:class="{ 'border-red-500': hasError('firstName') }"
:class="{ 'border-warning/50': hasError('firstName') }"
/>
<p v-if="hasError('firstName')" class="text-sm text-red-400">
<p v-if="hasError('firstName')" class="form-error">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-warning flex-shrink-0"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
{{ getError('firstName') }}
</p>
</div>
@@ -170,9 +188,10 @@ function getError(field: string): string {
type="text"
placeholder="Mustermann"
:disabled="loading"
:class="{ 'border-red-500': hasError('lastName') }"
:class="{ 'border-warning/50': hasError('lastName') }"
/>
<p v-if="hasError('lastName')" class="text-sm text-red-400">
<p v-if="hasError('lastName')" class="form-error">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-warning flex-shrink-0"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
{{ getError('lastName') }}
</p>
</div>
@@ -187,9 +206,10 @@ function getError(field: string): string {
v-model="form.dateOfBirth"
type="date"
:disabled="loading"
:class="{ 'border-red-500': hasError('dateOfBirth') }"
:class="{ 'border-warning/50': hasError('dateOfBirth') }"
/>
<p v-if="hasError('dateOfBirth')" class="text-sm text-red-400">
<p v-if="hasError('dateOfBirth')" class="form-error">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-warning flex-shrink-0"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
{{ getError('dateOfBirth') }}
</p>
</div>
@@ -205,9 +225,10 @@ function getError(field: string): string {
type="text"
placeholder="Musterstraße 123"
:disabled="loading"
:class="{ 'border-red-500': hasError('street') }"
:class="{ 'border-warning/50': hasError('street') }"
/>
<p v-if="hasError('street')" class="text-sm text-red-400">
<p v-if="hasError('street')" class="form-error">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-warning flex-shrink-0"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
{{ getError('street') }}
</p>
</div>
@@ -222,9 +243,10 @@ function getError(field: string): string {
placeholder="74072"
maxlength="5"
:disabled="loading"
:class="{ 'border-red-500': hasError('postCode') }"
:class="{ 'border-warning/50': hasError('postCode') }"
/>
<p v-if="hasError('postCode')" class="text-sm text-red-400">
<p v-if="hasError('postCode')" class="form-error">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-warning flex-shrink-0"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
{{ getError('postCode') }}
</p>
</div>
@@ -238,9 +260,10 @@ function getError(field: string): string {
type="text"
placeholder="Heilbronn"
:disabled="loading"
:class="{ 'border-red-500': hasError('city') }"
:class="{ 'border-warning/50': hasError('city') }"
/>
<p v-if="hasError('city')" class="text-sm text-red-400">
<p v-if="hasError('city')" class="form-error">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-warning flex-shrink-0"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
{{ getError('city') }}
</p>
</div>
@@ -249,7 +272,7 @@ function getError(field: string): string {
<div class="space-y-2">
<label for="countryCode" class="text-sm font-medium text-white">Land *</label>
<Select v-model="form.countryCode" :disabled="loading">
<SelectTrigger :class="{ 'border-red-500': hasError('countryCode') }">
<SelectTrigger :class="{ 'border-warning/50': hasError('countryCode') }">
<SelectValue placeholder="Bitte wählen" />
</SelectTrigger>
<SelectContent>
@@ -258,7 +281,8 @@ function getError(field: string): string {
<SelectItem value="CH">Schweiz</SelectItem>
</SelectContent>
</Select>
<p v-if="hasError('countryCode')" class="text-sm text-red-400">
<p v-if="hasError('countryCode')" class="form-error">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-warning flex-shrink-0"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
{{ getError('countryCode') }}
</p>
</div>
@@ -281,8 +305,9 @@ function getError(field: string): string {
<Button
type="submit"
:disabled="loading"
class="w-full bg-gradient-button bg-size-300 bg-left hover:bg-right transition-all duration-300 font-bold text-white shadow-lg hover:shadow-2xl"
size="lg"
variant="experimenta"
size="experimenta"
class="w-full"
>
<span v-if="!loading">Weiter zur Zahlung</span>
<span v-else class="flex items-center gap-2">

View File

@@ -172,7 +172,9 @@ const formatDate = (date: Date | string) => {
<h3 class="text-lg font-semibold text-white">Rechnungsadresse</h3>
<div class="p-4 rounded-lg bg-white/5 border border-white/10 space-y-1 text-sm">
<p class="text-white">
{{ formatSalutation(order.billingAddress.salutation) }}
<template v-if="order.billingAddress.salutation !== 'other'">
{{ formatSalutation(order.billingAddress.salutation) }}
</template>
{{ order.billingAddress.firstName }}
{{ order.billingAddress.lastName }}
</p>

View File

@@ -108,7 +108,7 @@ async function handleCheckout(checkoutData: CheckoutData) {
</div>
<!-- Error Alert -->
<Alert v-if="error" variant="destructive" class="mb-6">
<Alert v-if="error" variant="error" class="mb-6">
<AlertTitle>Fehler</AlertTitle>
<AlertDescription>{{ error }}</AlertDescription>
</Alert>

View File

@@ -112,7 +112,7 @@ onMounted(() => {
</div>
<!-- Error Alert -->
<Alert v-if="error" variant="destructive" class="mb-6">
<Alert v-if="error" variant="error" class="mb-6">
<AlertTitle>Fehler</AlertTitle>
<AlertDescription>{{ error }}</AlertDescription>
</Alert>
@@ -165,8 +165,9 @@ onMounted(() => {
<Button
@click="confirmOrder"
:disabled="isConfirming"
class="w-full bg-gradient-button bg-size-300 bg-left hover:bg-right transition-all duration-300 font-bold text-white shadow-lg hover:shadow-2xl"
size="lg"
variant="experimenta"
size="experimenta"
class="w-full"
>
<span v-if="!isConfirming" class="flex items-center justify-center gap-2">
<svg
@@ -209,8 +210,8 @@ onMounted(() => {
<!-- Back Link -->
<div class="text-center pt-4">
<NuxtLink to="/warenkorb" class="text-sm text-experimenta-accent hover:underline">
Zurück zum Warenkorb
<NuxtLink to="/checkout" class="text-sm text-experimenta-accent hover:underline">
Zurück zur Kasse
</NuxtLink>
</div>
</div>

View File

@@ -85,7 +85,7 @@ onMounted(() => {
<div class="container mx-auto px-4 py-8 max-w-4xl">
<!-- Error Alert -->
<Alert v-if="error" variant="destructive" class="mb-6">
<Alert v-if="error" variant="error" class="mb-6">
<AlertTitle>Fehler</AlertTitle>
<AlertDescription>{{ error }}</AlertDescription>
</Alert>
@@ -179,12 +179,8 @@ onMounted(() => {
<!-- Action Buttons -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<NuxtLink to="/">
<Button
variant="outline"
class="w-full border-white/20 hover:bg-white/10 text-white"
size="lg"
>
<NuxtLink to="/" class="w-full">
<Button variant="secondary" size="experimenta" class="w-full">
<svg
class="w-5 h-5 mr-2"
fill="none"
@@ -203,11 +199,8 @@ onMounted(() => {
</Button>
</NuxtLink>
<NuxtLink to="/experimenta">
<Button
class="w-full bg-gradient-button bg-size-300 bg-left hover:bg-right transition-all duration-300 font-bold text-white shadow-lg hover:shadow-2xl"
size="lg"
>
<NuxtLink to="/experimenta" class="w-full">
<Button variant="experimenta" size="experimenta" class="w-full">
<svg
class="w-5 h-5 mr-2"
fill="none"

View File

@@ -75,7 +75,7 @@ function handlePaymentSuccess() {
</div>
<!-- Error Alert -->
<Alert v-if="error" variant="destructive" class="mb-6">
<Alert v-if="error" variant="error" class="mb-6">
<AlertTitle>Fehler</AlertTitle>
<AlertDescription>
{{ error }}. Du wirst zum Warenkorb weitergeleitet...