Add a contact page with a form and essential contact details

Create a new contact page with a form that submits to an API endpoint. Register the route for this page and add a link to the footer.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 23852c00-4779-460a-9e0c-d09fee4b6c92
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ff98bd6b-82f0-4f2d-a2ce-62874cb0f0af
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/23852c00-4779-460a-9e0c-d09fee4b6c92/koutl3W
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sebastjanartic 2026-03-06 14:04:40 +00:00
parent a1dd9a1414
commit 72e4df3fe1
5 changed files with 275 additions and 0 deletions

View File

@ -16,6 +16,7 @@ import EmpfangPage from "@/pages/empfang";
import AboutPage from "@/pages/about";
import ImpressumPage from "@/pages/impressum";
import DatenschutzPage from "@/pages/datenschutz";
import KontaktPage from "@/pages/kontakt";
import AdminGalleryPage from "@/pages/admin-gallery";
import CookieConsent from "@/components/cookie-consent";
@ -35,6 +36,7 @@ function Router() {
<Route path="/ueber-uns" component={AboutPage} />
<Route path="/impressum" component={ImpressumPage} />
<Route path="/datenschutz" component={DatenschutzPage} />
<Route path="/kontakt" component={KontaktPage} />
<Route path="/admin/gallery" component={AdminGalleryPage} />
<Route component={NotFound} />
</Switch>

View File

@ -93,6 +93,9 @@ export default function Footer() {
<Link href="/ueber-uns">
<span className="text-muted-foreground cursor-pointer hover:text-primary transition-colors" data-testid="link-footer-ueber-uns">Über uns</span>
</Link>
<Link href="/kontakt">
<span className="text-muted-foreground cursor-pointer hover:text-primary transition-colors" data-testid="link-footer-kontakt">Kontakt</span>
</Link>
<Link href="/impressum">
<span className="text-muted-foreground cursor-pointer hover:text-primary transition-colors" data-testid="link-footer-impressum">Impressum</span>
</Link>

View File

@ -0,0 +1,241 @@
import { useState } from "react";
import { Mail, Phone, MapPin, Send, CheckCircle, AlertCircle } from "lucide-react";
import { SiFacebook, SiInstagram, SiYoutube, SiTiktok } from "react-icons/si";
import Header from "@/components/header";
import Footer from "@/components/footer";
import { usePageMeta } from "@/hooks/use-page-meta";
import { PageSideAds } from "@/components/adsense";
import { apiRequest } from "@/lib/queryClient";
export default function KontaktPage() {
usePageMeta("Kontakt - FOLX TV", "Kontaktieren Sie FOLX TV Ihr Fernsehsender für Volksmusik und Schlager.");
const [formData, setFormData] = useState({ name: "", email: "", subject: "", message: "" });
const [status, setStatus] = useState<"idle" | "sending" | "success" | "error">("idle");
const [errorMsg, setErrorMsg] = useState("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!formData.name || !formData.email || !formData.message) {
setErrorMsg("Bitte füllen Sie alle Pflichtfelder aus.");
setStatus("error");
return;
}
setStatus("sending");
setErrorMsg("");
try {
await apiRequest("POST", "/api/contact", formData);
setStatus("success");
setFormData({ name: "", email: "", subject: "", message: "" });
} catch (err: any) {
setErrorMsg(err.message || "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.");
setStatus("error");
}
};
return (
<div className="min-h-screen bg-background">
<Header />
<PageSideAds contentHalfWidth={384} />
<main className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="flex items-center gap-3 mb-8">
<Mail className="w-7 h-7 text-primary" />
<h1 className="text-3xl font-bold text-foreground" data-testid="text-kontakt-title">
Kontakt
</h1>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-10">
<div className="space-y-6">
<p className="text-muted-foreground text-[15px] leading-relaxed">
Haben Sie Fragen, Anregungen oder möchten Sie mit uns in Kontakt treten? Wir freuen uns auf Ihre Nachricht!
</p>
<div className="space-y-4">
<a
href="tel:+491725675800"
className="flex items-start gap-3 group"
data-testid="link-kontakt-phone"
>
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0 group-hover:bg-primary/20 transition-colors">
<Phone className="w-5 h-5 text-primary" />
</div>
<div>
<p className="text-xs text-muted-foreground">Telefon</p>
<p className="text-foreground font-medium group-hover:text-primary transition-colors" data-testid="text-kontakt-phone">+49 172 567 58 00</p>
</div>
</a>
<a
href="mailto:office@folx.tv"
className="flex items-start gap-3 group"
data-testid="link-kontakt-email"
>
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0 group-hover:bg-primary/20 transition-colors">
<Mail className="w-5 h-5 text-primary" />
</div>
<div>
<p className="text-xs text-muted-foreground">E-Mail</p>
<p className="text-foreground font-medium group-hover:text-primary transition-colors" data-testid="text-kontakt-email">office@folx.tv</p>
</div>
</a>
<div className="flex items-start gap-3">
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0">
<MapPin className="w-5 h-5 text-primary" />
</div>
<div>
<p className="text-xs text-muted-foreground">Adresse</p>
<p className="text-foreground font-medium" data-testid="text-kontakt-address">BoldFrame Productions d.o.o.</p>
<p className="text-muted-foreground text-sm">Sokolska ulica 46</p>
<p className="text-muted-foreground text-sm">2000 Maribor, Slowenien</p>
</div>
</div>
</div>
<div className="pt-2">
<p className="text-xs text-muted-foreground mb-3">Folgen Sie uns</p>
<div className="flex gap-3">
{[
{ href: "https://www.facebook.com/folxtv/", icon: SiFacebook, label: "Facebook" },
{ href: "https://www.instagram.com/folxtv/", icon: SiInstagram, label: "Instagram" },
{ href: "https://www.youtube.com/@folxmtv", icon: SiYoutube, label: "YouTube" },
{ href: "https://www.tiktok.com/@folxtv", icon: SiTiktok, label: "TikTok" },
].map((s) => (
<a
key={s.label}
href={s.href}
target="_blank"
rel="noopener noreferrer"
className="w-10 h-10 rounded-lg bg-card border border-card-border flex items-center justify-center text-muted-foreground hover:text-primary hover:border-primary/30 transition-colors"
aria-label={s.label}
data-testid={`link-kontakt-${s.label.toLowerCase()}`}
>
<s.icon className="w-4 h-4" />
</a>
))}
</div>
</div>
</div>
<div className="bg-card border border-card-border rounded-xl p-6" data-testid="section-kontakt-form">
<h2 className="text-lg font-semibold text-card-foreground mb-4">Nachricht senden</h2>
{status === "success" ? (
<div className="flex flex-col items-center justify-center py-8 text-center" data-testid="kontakt-success">
<CheckCircle className="w-12 h-12 text-green-500 mb-3" />
<h3 className="text-foreground font-semibold text-lg mb-1">Vielen Dank!</h3>
<p className="text-muted-foreground text-sm">Ihre Nachricht wurde erfolgreich gesendet. Wir melden uns so schnell wie möglich bei Ihnen.</p>
<button
onClick={() => setStatus("idle")}
className="mt-4 text-primary text-sm hover:underline"
data-testid="button-kontakt-another"
>
Weitere Nachricht senden
</button>
</div>
) : (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="name" className="block text-sm font-medium text-card-foreground mb-1">
Name <span className="text-red-500">*</span>
</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary/50"
placeholder="Ihr Name"
data-testid="input-kontakt-name"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-card-foreground mb-1">
E-Mail <span className="text-red-500">*</span>
</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary/50"
placeholder="ihre@email.com"
data-testid="input-kontakt-email"
/>
</div>
<div>
<label htmlFor="subject" className="block text-sm font-medium text-card-foreground mb-1">
Betreff
</label>
<select
id="subject"
name="subject"
value={formData.subject}
onChange={handleChange}
className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary/50"
data-testid="select-kontakt-subject"
>
<option value="">Bitte wählen...</option>
<option value="Allgemeine Anfrage">Allgemeine Anfrage</option>
<option value="Programmvorschlag">Programmvorschlag</option>
<option value="Technischer Support">Technischer Support</option>
<option value="Werbung & Kooperationen">Werbung & Kooperationen</option>
<option value="Presse">Presse</option>
<option value="Sonstiges">Sonstiges</option>
</select>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium text-card-foreground mb-1">
Nachricht <span className="text-red-500">*</span>
</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
rows={5}
className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary/50 resize-none"
placeholder="Ihre Nachricht..."
data-testid="input-kontakt-message"
/>
</div>
{status === "error" && (
<div className="flex items-center gap-2 text-red-500 text-sm" data-testid="kontakt-error">
<AlertCircle className="w-4 h-4 flex-shrink-0" />
<span>{errorMsg}</span>
</div>
)}
<button
type="submit"
disabled={status === "sending"}
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 bg-primary hover:bg-primary/90 disabled:bg-primary/50 text-primary-foreground font-medium text-sm rounded-lg transition-colors"
data-testid="button-kontakt-submit"
>
{status === "sending" ? (
<div className="w-4 h-4 border-2 border-primary-foreground/50 border-t-primary-foreground rounded-full animate-spin" />
) : (
<Send className="w-4 h-4" />
)}
{status === "sending" ? "Wird gesendet..." : "Nachricht senden"}
</button>
</form>
)}
</div>
</div>
</main>
<Footer />
</div>
);
}

View File

@ -0,0 +1 @@
[]

View File

@ -462,6 +462,34 @@ export async function registerRoutes(
}
});
app.post("/api/contact", async (req, res) => {
try {
const { name, email, subject, message } = req.body;
if (!name || !email || !message) {
return res.status(400).json({ message: "Name, E-Mail und Nachricht sind erforderlich." });
}
const contactPath = path.join(process.cwd(), "server/contact-messages.json");
let messages: any[] = [];
if (fs.existsSync(contactPath)) {
messages = JSON.parse(fs.readFileSync(contactPath, "utf-8"));
}
messages.push({
id: Date.now(),
name,
email,
subject: subject || "Keine Angabe",
message,
createdAt: new Date().toISOString(),
read: false,
});
fs.writeFileSync(contactPath, JSON.stringify(messages, null, 2));
console.log(`[contact] New message from ${name} <${email}>: ${subject || "No subject"}`);
res.json({ ok: true });
} catch (err: any) {
res.status(500).json({ message: err.message });
}
});
app.get("/api/gallery/artists", (_req, res) => {
try {
const aPath = path.join(process.cwd(), "server/gallery-artist-overrides.json");