Initial upload of BB-Apartment 2026 website.

Adds the complete Next.js website including i18n routing, house and
apartment pages, Beds24 v2 integration for live availability/pricing,
Stripe checkout, contact forms, Docker setup and deployment docs.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Markus
2026-08-19 22:54:49 +02:00
co-authored by Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent 232770afe7
commit 2b24334bfa
153 changed files with 5621 additions and 176 deletions
+125
View File
@@ -0,0 +1,125 @@
"use client";
import { useState } from "react";
import { Mail, Send, CheckCircle2 } from "lucide-react";
interface NoAvailabilityContactProps {
locale: string;
/** e.g. the searched arrival/departure dates, sent along for context. */
context?: string;
}
export function NoAvailabilityContact({ locale, context }: NoAvailabilityContactProps) {
const [email, setEmail] = useState("");
const [message, setMessage] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [sent, setSent] = useState(false);
const isDe = locale === "de";
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!email || !message) return;
setLoading(true);
setError("");
try {
const res = await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, message, context }),
});
if (!res.ok) {
throw new Error("Failed to send message");
}
setSent(true);
} catch {
setError(
isDe
? "Ihre Nachricht konnte nicht gesendet werden. Bitte versuchen Sie es erneut oder schreiben Sie uns direkt an info@bb-apartment.de."
: "Your message could not be sent. Please try again or email us directly at info@bb-apartment.de."
);
} finally {
setLoading(false);
}
}
if (sent) {
return (
<div className="bg-brand-green/10 border-2 border-brand-green/30 rounded-2xl p-8 text-center max-w-xl mx-auto">
<CheckCircle2 className="w-10 h-10 text-brand-green mx-auto mb-3" />
<h3 className="text-xl font-bold text-brand-grey-dark mb-2">
{isDe ? "Nachricht gesendet!" : "Message sent!"}
</h3>
<p className="text-brand-grey">
{isDe
? "Danke für Ihre Anfrage. Wir melden uns so schnell wie möglich bei Ihnen."
: "Thank you for reaching out. We'll get back to you as soon as possible."}
</p>
</div>
);
}
return (
<div className="bg-white border-2 border-brand-grey-soft rounded-2xl p-8 max-w-xl mx-auto">
<div className="flex items-center gap-3 mb-2">
<Mail className="w-6 h-6 text-brand-green" />
<h3 className="text-xl font-bold text-brand-grey-dark">
{isDe ? "Kein passendes Apartment gefunden?" : "Can't find what you're looking for?"}
</h3>
</div>
<p className="text-brand-grey mb-6">
{isDe
? "Schreiben Sie uns kurz Ihre Wunschtermine und Anforderungen — wir melden uns persönlich bei Ihnen, auch für individuelle Anfragen oder Wartelisten."
: "Tell us your preferred dates and requirements — we'll get back to you personally, including for custom requests or waitlists."}
</p>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-brand-grey-dark mb-1">
{isDe ? "E-Mail" : "Email"}
</label>
<input
type="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-3 rounded-xl border border-brand-grey-soft focus:border-brand-green focus:ring-1 focus:ring-brand-green outline-none"
/>
</div>
<div>
<label className="block text-sm font-medium text-brand-grey-dark mb-1">
{isDe ? "Ihre Nachricht" : "Your message"}
</label>
<textarea
required
rows={5}
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder={
isDe
? "z.B. gewünschter Zeitraum, Anzahl Personen, besondere Wünsche..."
: "e.g. preferred dates, number of guests, special requests..."
}
className="w-full px-4 py-3 rounded-xl border border-brand-grey-soft focus:border-brand-green focus:ring-1 focus:ring-brand-green outline-none"
/>
</div>
{error && (
<div className="bg-red-50 text-red-600 rounded-lg p-3 text-sm">{error}</div>
)}
<button
type="submit"
disabled={loading}
className="inline-flex items-center justify-center gap-2 w-full bg-brand-green text-white font-semibold px-8 py-4 rounded-full hover:bg-brand-green-dark transition-colors disabled:opacity-50"
>
{loading ? (isDe ? "Wird gesendet..." : "Sending...") : isDe ? "Nachricht senden" : "Send message"}
<Send className="w-4 h-4" />
</button>
</form>
</div>
);
}