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
+206
View File
@@ -0,0 +1,206 @@
import { notFound } from "next/navigation";
import { useTranslations } from "next-intl";
import { getTranslations } from "next-intl/server";
import { getHouseBySlug } from "@/src/config/apartments";
import { ApartmentCard } from "@/components/ui/ApartmentCard";
import { HouseHighlights } from "@/components/ui/HouseHighlights";
import { MapPin, Home, Car, Clock, Navigation } from "lucide-react";
import Image from "next/image";
export async function generateMetadata({
params: { locale, houseSlug },
}: {
params: { locale: string; houseSlug: string };
}) {
const house = getHouseBySlug(houseSlug);
if (!house) return { title: "Not Found" };
const t = await getTranslations({ locale, namespace: "metadata" });
return {
title: `${house.nameEn} - ${t("title")}`,
description: house.descriptionEn,
};
}
export default function HousePage({
params: { locale, houseSlug },
}: {
params: { locale: string; houseSlug: string };
}) {
const t = useTranslations("houses");
const house = getHouseBySlug(houseSlug);
if (!house) notFound();
const displayName = locale === "de" ? house.name : house.nameEn;
const displayDescription =
locale === "de" ? house.description : house.descriptionEn;
const displayCountry = locale === "de" ? house.country : house.countryEn;
return (
<div className="py-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="relative h-80 md:h-96 rounded-2xl overflow-hidden mb-12">
<Image
src={house.images[0] || "/images/placeholder.jpg"}
alt={displayName}
fill
className="object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
<div className="absolute bottom-0 left-0 p-8 text-white">
<h1 className="text-4xl font-bold mb-2 text-white">{displayName}</h1>
<div className="flex items-center gap-2 text-white/90">
<MapPin className="w-5 h-5" />
<span>
{house.address}, {house.zipCode} {house.city}, {displayCountry}
</span>
</div>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12">
<div className="lg:col-span-2">
<h2 className="text-2xl font-bold mb-4">
{t("overview")}
</h2>
<p className="text-brand-grey leading-relaxed mb-8">
{displayDescription}
</p>
<HouseHighlights house={house} locale={locale} />
<h2 className="text-2xl font-bold mb-6">
{t("apartments")}
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{house.apartments.map((apartment) => (
<ApartmentCard
key={apartment.id}
apartment={apartment}
locale={locale}
/>
))}
</div>
</div>
<div className="flex flex-col gap-8 h-fit">
<div className="bg-brand-grey-soft rounded-2xl p-8 h-fit">
<div className="flex items-center gap-3 mb-4">
<Home className="w-6 h-6 text-brand-green" />
<h3 className="text-xl font-bold">{displayName}</h3>
</div>
<p className="text-brand-grey text-sm mb-4">
{house.apartments.length} {locale === "de" ? "Apartments" : "Apartments"}
</p>
<div className="text-sm text-brand-grey">
<p className="mb-2">
<strong>{locale === "de" ? "Adresse:" : "Address:"}</strong> {house.address}
</p>
<p>
<strong>{locale === "de" ? "Stadt:" : "City:"}</strong> {house.zipCode} {house.city}
</p>
</div>
</div>
<div className="bg-white rounded-2xl border border-brand-grey-soft shadow-sm p-6 h-fit">
<div className="flex items-center gap-2 mb-4">
<Navigation className="w-5 h-5 text-brand-green" />
<h2 className="text-lg font-bold">
{locale === "de" ? "Lage & Entfernungen" : "Location & Distances"}
</h2>
</div>
<div className="flex flex-col gap-3 mb-4">
{house.id === "herdweg" ? (
<>
<a
href="https://www.google.com/maps/dir/Herdweg+71,+71032+Böblingen/Panzer+Kaserne+Böblingen"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-3 p-3 bg-brand-green text-white rounded-xl hover:bg-brand-green-dark transition-colors"
>
<Car className="w-5 h-5 shrink-0" />
<div>
<p className="font-bold text-sm">
{locale === "de" ? "Panzer Kaserne – gleiche Straße" : "Panzer Kaserne – Same Street"}
</p>
<p className="text-sm text-white/85">
{locale === "de" ? "Kein Abbiegen nötig" : "Zero turns needed"}
</p>
</div>
</a>
<a
href="https://www.google.com/maps/dir/Herdweg+71,+71032+Böblingen/Böblingen+Zentrum"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-3 p-3 bg-brand-green/10 rounded-xl hover:bg-brand-green/20 transition-colors"
>
<Clock className="w-5 h-5 text-brand-green shrink-0" />
<div>
<p className="font-bold text-brand-grey-dark text-sm">
{locale === "de" ? "Stadtzentrum Böblingen" : "Böblingen City Center"}
</p>
<p className="text-sm text-brand-grey">5–8 min</p>
</div>
</a>
</>
) : (
<>
<a
href="https://www.google.com/maps/dir/Schönaicher+Straße+26,+71032+Böblingen/Daimler+Werk+Sindelfingen"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-3 p-3 bg-brand-green/10 rounded-xl hover:bg-brand-green/20 transition-colors"
>
<Car className="w-5 h-5 text-brand-green shrink-0" />
<div>
<p className="font-bold text-brand-grey-dark text-sm">
{locale === "de" ? "Daimler Werk Sindelfingen" : "Daimler Plant Sindelfingen"}
</p>
<p className="text-sm text-brand-grey">10–15 min</p>
</div>
</a>
<a
href="https://www.google.com/maps/dir/Schönaicher+Straße+26,+71032+Böblingen/Böblingen+Zentrum"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-3 p-3 bg-brand-green/10 rounded-xl hover:bg-brand-green/20 transition-colors"
>
<Clock className="w-5 h-5 text-brand-green shrink-0" />
<div>
<p className="font-bold text-brand-grey-dark text-sm">
{locale === "de" ? "Stadtzentrum Böblingen" : "Böblingen City Center"}
</p>
<p className="text-sm text-brand-grey">&lt; 5 min</p>
</div>
</a>
</>
)}
</div>
<div className="relative w-full h-48 rounded-xl overflow-hidden border border-brand-grey-soft">
<iframe
src={`https://maps.google.com/maps?q=${encodeURIComponent(`${house.address}, ${house.zipCode} ${house.city}`)}&z=15&output=embed`}
width="100%"
height="100%"
style={{ border: 0 }}
allowFullScreen
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
title={displayName}
className="grayscale-[20%] hover:grayscale-0 transition-all duration-500"
/>
</div>
<p className="text-xs text-brand-grey mt-3 text-center">
{locale === "de"
? "Karte ist interaktiv – klicken Sie auf einen Ort, um die Route zu planen."
: "Map is interactive – click a destination to plan the route."}
</p>
</div>
</div>
</div>
</div>
</div>
);
}