feat(T02): Static pages – home, referenzen, kontakt, legal (impressum/datenschutz/agb)
This commit is contained in:
+280
-4
@@ -1,9 +1,285 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-text mb-4">Kontakt</h1>
|
||||
<p class="text-text-muted">Kontaktformular wird hier angezeigt.</p>
|
||||
<h1 class="text-3xl font-bold text-text mb-2" data-testid="kontakt-headline">Kontakt</h1>
|
||||
<p class="text-text-muted mb-8">Planen Sie mit uns Ihre nächste Veranstaltung.</p>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-12">
|
||||
<!-- Left Column: Addresses + Persons -->
|
||||
<div class="space-y-6">
|
||||
<!-- Office Address -->
|
||||
<div class="bg-panel border border-border-default rounded-lg p-6" data-testid="office-card">
|
||||
<h2 class="text-accent text-sm font-bold mb-3">Büro</h2>
|
||||
<p class="text-text mb-1">Hammerschmidt u. Mössle GbR</p>
|
||||
<p class="text-text-muted text-sm mb-1">Grockelhofen 10</p>
|
||||
<p class="text-text-muted text-sm mb-3">89340 Leipheim</p>
|
||||
<p class="text-text-muted text-sm mb-1">Tel: <a href="tel:+498221204433" class="text-accent hover:underline">+49 8221 204433</a></p>
|
||||
<p class="text-text-muted text-sm">E-Mail: <a href="mailto:info@hms-licht-ton.de" class="text-accent hover:underline">info@hms-licht-ton.de</a></p>
|
||||
</div>
|
||||
|
||||
<!-- Warehouse Address -->
|
||||
<div class="bg-panel border border-border-default rounded-lg p-6" data-testid="warehouse-card">
|
||||
<h2 class="text-accent text-sm font-bold mb-3">Lager / Werkstatt</h2>
|
||||
<p class="text-text-muted text-sm mb-1">Ellzee</p>
|
||||
<p class="text-text-muted text-sm mb-3">Bayern, Deutschland</p>
|
||||
<p class="text-text-muted text-sm">Besuche nach Terminvereinbarung.</p>
|
||||
</div>
|
||||
|
||||
<!-- Opening Hours -->
|
||||
<div class="bg-panel border border-border-default rounded-lg p-6" data-testid="hours-card">
|
||||
<h2 class="text-accent text-sm font-bold mb-3">Öffnungszeiten</h2>
|
||||
<div class="space-y-1 text-text-muted text-sm">
|
||||
<p>Mo – Do: 08:00 – 17:00</p>
|
||||
<p>Fr: 08:00 – 14:00</p>
|
||||
<p>Sa – So: Geschlossen</p>
|
||||
<p class="text-accent text-xs mt-2">Termine außerhalb der Öffnungszeiten nach Vereinbarung.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact Persons -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div v-for="person in contactPersons" :key="person.name" class="bg-panel border border-border-default rounded-lg p-5" :data-testid="'contact-person-' + person.name.split(' ')[0].toLowerCase()">
|
||||
<div class="w-16 h-16 rounded-full bg-card border border-border-default mb-3 flex items-center justify-center text-secondary">
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
|
||||
<circle cx="16" cy="12" r="5" fill="none" stroke="currentColor" stroke-width="2" />
|
||||
<path d="M6 28 Q16 20 26 28" fill="none" stroke="currentColor" stroke-width="2" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="text-text text-sm font-bold">{{ person.name }}</p>
|
||||
<p class="text-accent text-xs mb-1">{{ person.role }}</p>
|
||||
<a :href="'mailto:' + person.email" class="text-text-muted text-xs hover:text-accent transition-colors duration-fast">{{ person.email }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Contact Form -->
|
||||
<div class="bg-panel border border-border-default rounded-lg p-6" data-testid="contact-form-card">
|
||||
<h2 class="text-2xl font-bold mb-6">Anfrage senden</h2>
|
||||
|
||||
<!-- Success State -->
|
||||
<div v-if="submitSuccess" class="bg-success-bg border border-success/30 rounded-lg p-6 text-center" data-testid="success-message">
|
||||
<p class="text-success font-bold text-lg mb-2">Vielen Dank!</p>
|
||||
<p class="text-text-muted text-sm">Ihre Nachricht wurde erfolgreich gesendet. Wir melden uns in Kürze bei Ihnen.</p>
|
||||
<button class="btn-secondary mt-4" @click="resetForm" data-testid="reset-form">Neue Nachricht</button>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div v-else-if="submitError" class="bg-error-bg border border-error/30 rounded-lg p-6 text-center" data-testid="error-message">
|
||||
<p class="text-error font-bold text-lg mb-2">Fehler</p>
|
||||
<p class="text-text-muted text-sm mb-4">Beim Senden ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.</p>
|
||||
<button class="btn-primary" @click="submitError = false" data-testid="retry-submit">Erneut versuchen</button>
|
||||
</div>
|
||||
|
||||
<!-- Form -->
|
||||
<form v-else @submit.prevent="handleSubmit" novalidate data-testid="contact-form">
|
||||
<!-- Name -->
|
||||
<div class="mb-4">
|
||||
<label for="name" class="block text-text-muted text-sm mb-1">Name *</label>
|
||||
<input
|
||||
id="name"
|
||||
v-model="form.name"
|
||||
type="text"
|
||||
class="w-full bg-surface border border-border-default rounded-md px-3 py-2 text-text focus:border-accent focus:outline-none"
|
||||
:class="{ 'border-error': errors.name }"
|
||||
data-testid="form-name"
|
||||
@blur="validateField('name')"
|
||||
/>
|
||||
<p v-if="errors.name" class="text-error text-xs mt-1" data-testid="error-name">{{ errors.name }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="mb-4">
|
||||
<label for="email" class="block text-text-muted text-sm mb-1">E-Mail *</label>
|
||||
<input
|
||||
id="email"
|
||||
v-model="form.email"
|
||||
type="email"
|
||||
class="w-full bg-surface border border-border-default rounded-md px-3 py-2 text-text focus:border-accent focus:outline-none"
|
||||
:class="{ 'border-error': errors.email }"
|
||||
data-testid="form-email"
|
||||
@blur="validateField('email')"
|
||||
/>
|
||||
<p v-if="errors.email" class="text-error text-xs mt-1" data-testid="error-email">{{ errors.email }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Phone -->
|
||||
<div class="mb-4">
|
||||
<label for="phone" class="block text-text-muted text-sm mb-1">Telefon</label>
|
||||
<input
|
||||
id="phone"
|
||||
v-model="form.phone"
|
||||
type="tel"
|
||||
class="w-full bg-surface border border-border-default rounded-md px-3 py-2 text-text focus:border-accent focus:outline-none"
|
||||
data-testid="form-phone"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Message -->
|
||||
<div class="mb-4">
|
||||
<label for="message" class="block text-text-muted text-sm mb-1">Nachricht *</label>
|
||||
<textarea
|
||||
id="message"
|
||||
v-model="form.message"
|
||||
rows="5"
|
||||
class="w-full bg-surface border border-border-default rounded-md px-3 py-2 text-text focus:border-accent focus:outline-none"
|
||||
:class="{ 'border-error': errors.message }"
|
||||
data-testid="form-message"
|
||||
@blur="validateField('message')"
|
||||
></textarea>
|
||||
<p v-if="errors.message" class="text-error text-xs mt-1" data-testid="error-message-field">{{ errors.message }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Privacy Consent -->
|
||||
<div class="mb-6">
|
||||
<label class="flex items-start gap-3 cursor-pointer">
|
||||
<input
|
||||
v-model="form.privacy"
|
||||
type="checkbox"
|
||||
class="mt-1 w-4 h-4 rounded border-border-default bg-surface accent-accent"
|
||||
:class="{ 'border-error': errors.privacy }"
|
||||
data-testid="form-privacy"
|
||||
/>
|
||||
<span class="text-text-muted text-sm">
|
||||
Ich habe die <NuxtLink to="/datenschutz" class="text-accent hover:underline">Datenschutzerklärung</NuxtLink> gelesen und stimme zu, dass meine Daten zur Bearbeitung meiner Anfrage gespeichert werden. *
|
||||
</span>
|
||||
</label>
|
||||
<p v-if="errors.privacy" class="text-error text-xs mt-1" data-testid="error-privacy">{{ errors.privacy }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<button
|
||||
type="submit"
|
||||
class="btn-primary w-full"
|
||||
:disabled="isSubmitting"
|
||||
data-testid="form-submit"
|
||||
>
|
||||
{{ isSubmitting ? "Wird gesendet..." : "Anfrage senden" }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
useHead({ title: "Kontakt – HMS Licht & Ton", meta: [{ name: "robots", content: "noindex, nofollow, noarchive, nosnippet" }] });
|
||||
</script>
|
||||
import { reactive, ref } from "vue";
|
||||
|
||||
useHead({
|
||||
title: "Kontakt – HMS Licht & Ton",
|
||||
meta: [{ name: "robots", content: "noindex, nofollow, noarchive, nosnippet" }],
|
||||
});
|
||||
|
||||
const contactPersons = [
|
||||
{ name: "Markus Hammerschmidt", role: "Geschäftsführer – Ton & Verkauf", email: "m.hammerschmidt@hms-licht-ton.de" },
|
||||
{ name: "Thomas Mössle", role: "Geschäftsführer – Licht & Rigging", email: "t.moessle@hms-licht-ton.de" },
|
||||
];
|
||||
|
||||
interface ContactForm {
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
message: string;
|
||||
privacy: boolean;
|
||||
}
|
||||
|
||||
interface FormErrors {
|
||||
name?: string;
|
||||
email?: string;
|
||||
message?: string;
|
||||
privacy?: string;
|
||||
}
|
||||
|
||||
const form = reactive<ContactForm>({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
message: "",
|
||||
privacy: false,
|
||||
});
|
||||
|
||||
const errors = reactive<FormErrors>({});
|
||||
const isSubmitting = ref(false);
|
||||
const submitSuccess = ref(false);
|
||||
const submitError = ref(false);
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
function validateField(field: keyof FormErrors): boolean {
|
||||
switch (field) {
|
||||
case "name":
|
||||
if (!form.name.trim()) {
|
||||
errors.name = "Bitte geben Sie Ihren Namen ein.";
|
||||
return false;
|
||||
}
|
||||
delete errors.name;
|
||||
return true;
|
||||
case "email":
|
||||
if (!form.email.trim()) {
|
||||
errors.email = "Bitte geben Sie Ihre E-Mail-Adresse ein.";
|
||||
return false;
|
||||
}
|
||||
if (!emailRegex.test(form.email)) {
|
||||
errors.email = "Bitte geben Sie eine gültige E-Mail-Adresse ein.";
|
||||
return false;
|
||||
}
|
||||
delete errors.email;
|
||||
return true;
|
||||
case "message":
|
||||
if (!form.message.trim()) {
|
||||
errors.message = "Bitte geben Sie eine Nachricht ein.";
|
||||
return false;
|
||||
}
|
||||
delete errors.message;
|
||||
return true;
|
||||
case "privacy":
|
||||
if (!form.privacy) {
|
||||
errors.privacy = "Bitte stimmen Sie der Datenschutzerklärung zu.";
|
||||
return false;
|
||||
}
|
||||
delete errors.privacy;
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function validateAll(): boolean {
|
||||
const nameValid = validateField("name");
|
||||
const emailValid = validateField("email");
|
||||
const messageValid = validateField("message");
|
||||
const privacyValid = validateField("privacy");
|
||||
return nameValid && emailValid && messageValid && privacyValid;
|
||||
}
|
||||
|
||||
async function handleSubmit(): Promise<void> {
|
||||
if (!validateAll()) return;
|
||||
|
||||
isSubmitting.value = true;
|
||||
try {
|
||||
await $fetch("/api/contact", {
|
||||
method: "POST",
|
||||
body: {
|
||||
name: form.name,
|
||||
email: form.email,
|
||||
phone: form.phone,
|
||||
message: form.message,
|
||||
},
|
||||
});
|
||||
submitSuccess.value = true;
|
||||
} catch (err) {
|
||||
submitError.value = true;
|
||||
} finally {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm(): void {
|
||||
form.name = "";
|
||||
form.email = "";
|
||||
form.phone = "";
|
||||
form.message = "";
|
||||
form.privacy = false;
|
||||
Object.keys(errors).forEach((k) => delete errors[k as keyof FormErrors]);
|
||||
submitSuccess.value = false;
|
||||
submitError.value = false;
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user