diff --git a/.gitignore b/.gitignore
index 5ef6a52..e36841c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,7 @@
!.yarn/plugins
!.yarn/releases
!.yarn/versions
-
+/docs
# testing
/coverage
diff --git a/app/[locale]/galeri/page.tsx b/app/[locale]/galeri/page.tsx
new file mode 100644
index 0000000..0859d97
--- /dev/null
+++ b/app/[locale]/galeri/page.tsx
@@ -0,0 +1,40 @@
+import { setRequestLocale } from "next-intl/server";
+import Image from "next/image";
+import { mockData } from "@/lib/mock-data";
+
+export default async function GaleriPage({ params }: { params: Promise<{ locale: string }> }) {
+ const { locale } = await params;
+ setRequestLocale(locale);
+
+ // Combine hero slides and accommodation images for a rich gallery
+ const allImages = [
+ ...mockData.heroSlides.map((url, i) => ({ id: `slide-${i}`, url, title: "Kordon Apart" })),
+ ...mockData.accommodations.map(r => ({ id: r.id, url: r.image, title: r.name }))
+ ];
+
+ return (
+
+
+
Galeri
+
+ Kordon Apart'ın eşsiz atmosferini, mimarisini ve Fethiye'nin güzelliklerini keşfedin.
+
+
+
+
+ {allImages.map((img) => (
+
+ ))}
+
+
+ );
+}
diff --git a/app/[locale]/iletisim/page.tsx b/app/[locale]/iletisim/page.tsx
new file mode 100644
index 0000000..8098af8
--- /dev/null
+++ b/app/[locale]/iletisim/page.tsx
@@ -0,0 +1,17 @@
+import { setRequestLocale, getTranslations } from 'next-intl/server';
+import { ContactContent } from '@/components/contact-content';
+
+export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
+ const { locale } = await params;
+ const t = await getTranslations({ locale, namespace: 'nav' });
+ return {
+ title: `${t('contact')} - Kordon Apart`,
+ };
+}
+
+export default async function ContactPage({ params }: { params: Promise<{ locale: string }> }) {
+ const { locale } = await params;
+ setRequestLocale(locale);
+
+ return ;
+}
diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx
index e64a5e9..760ceb7 100644
--- a/app/[locale]/layout.tsx
+++ b/app/[locale]/layout.tsx
@@ -1,24 +1,28 @@
import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
+import { Inter, Montserrat } from "next/font/google";
import { NextIntlClientProvider } from 'next-intl';
import { getMessages, setRequestLocale } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';
+import { Navbar } from '@/components/navbar';
+import { Footer } from '@/components/footer';
+import { ThemeProvider } from "@/components/theme-provider";
+import { Toaster } from "sonner";
import "../globals.css";
-const geistSans = Geist({
- variable: "--font-geist-sans",
+const inter = Inter({
+ variable: "--font-inter",
subsets: ["latin"],
});
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
+const montserrat = Montserrat({
+ variable: "--font-montserrat",
subsets: ["latin"],
});
export const metadata: Metadata = {
- title: "Boilerplate App",
- description: "Next.js boilerplate with next-intl and NextAuth",
+ title: "Kordon Apart - Fethiye",
+ description: "Kordon Apart Fethiye / Merkez - Delüks ve Premium Apart Odalar",
};
export function generateStaticParams() {
@@ -44,12 +48,25 @@ export default async function RootLayout({
return (
-
-
- {children}
-
+
+
+
+
+
+ {children}
+
+
+
+
+
);
diff --git a/app/[locale]/odalar/[slug]/page.tsx b/app/[locale]/odalar/[slug]/page.tsx
new file mode 100644
index 0000000..7a80f3c
--- /dev/null
+++ b/app/[locale]/odalar/[slug]/page.tsx
@@ -0,0 +1,50 @@
+import { notFound } from "next/navigation";
+import Image from "next/image";
+import { setRequestLocale } from "next-intl/server";
+import { mockData } from "@/lib/mock-data";
+
+export default async function RoomDetailPage({
+ params
+}: {
+ params: Promise<{ locale: string; slug: string }>
+}) {
+ const { locale, slug } = await params;
+ setRequestLocale(locale);
+
+ const room = mockData.accommodations.find(r => r.slug === slug);
+ if (!room) {
+ notFound();
+ }
+
+ return (
+
+
+
+
+
+
+ {room.type}
+
+
{room.name}
+
+ {room.location} • {room.bedrooms} Yatak Odası • {room.capacity} Kişi
+
+
+
+
+
+
Oda Detayları
+
+ Bu oda, konforunuz için özenle tasarlanmış benzersiz bir yaşam alanı sunar.
+ {room.name}, muhteşem Fethiye manzarasıyla birleşen lüks dokunuşlarla unutulmaz bir konaklama deneyimi vadediyor.
+
+
+
+ );
+}
diff --git a/app/[locale]/odalar/page.tsx b/app/[locale]/odalar/page.tsx
new file mode 100644
index 0000000..ed94b2c
--- /dev/null
+++ b/app/[locale]/odalar/page.tsx
@@ -0,0 +1,14 @@
+import { useTranslations } from "next-intl";
+import { setRequestLocale } from "next-intl/server";
+import { RoomList } from "@/components/home/room-list";
+
+export default async function OdalarPage({ params }: { params: Promise<{ locale: string }> }) {
+ const { locale } = await params;
+ setRequestLocale(locale);
+
+ return (
+
+
+
+ );
+}
diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx
index e86607f..d6fd96b 100644
--- a/app/[locale]/page.tsx
+++ b/app/[locale]/page.tsx
@@ -1,48 +1,19 @@
-import { getTranslations, setRequestLocale } from 'next-intl/server'
-import Link from 'next/link'
+import { setRequestLocale } from 'next-intl/server';
+import { Hero } from '@/components/home/hero';
+import { RoomList } from '@/components/home/room-list';
+import { Services } from '@/components/home/services';
+import { Newsletter } from '@/components/home/newsletter';
export default async function HomePage({ params }: { params: Promise<{ locale: string }> }) {
- const { locale } = await params
- setRequestLocale(locale)
-
- const t = await getTranslations('hero')
- const nav = await getTranslations('nav')
- const footer = await getTranslations('footer')
+ const { locale } = await params;
+ setRequestLocale(locale);
return (
-
-
- Boilerplate
-
- {nav('home')}
- {nav('about')}
- {nav('contact')}
- Login
-
-
-
-
-
- {t('title')}
-
-
- This is a dummy landing page to demonstrate the localization setup using next-intl.
-
-
-
- {t('cta')}
-
-
-
- TR
- EN
-
-
-
-
-
-
- )
+ <>
+
+
+
+
+ >
+ );
}
diff --git a/app/globals.css b/app/globals.css
index c56032b..02e385c 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -5,116 +5,125 @@
@custom-variant dark (&:is(.dark *));
@theme inline {
+ /* Shadcn UI Variables */
--color-background: var(--background);
--color-foreground: var(--foreground);
- --font-sans: var(--font-sans);
- --font-mono: var(--font-geist-mono);
- --font-heading: var(--font-sans);
- --color-sidebar-ring: var(--sidebar-ring);
- --color-sidebar-border: var(--sidebar-border);
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
- --color-sidebar-accent: var(--sidebar-accent);
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
- --color-sidebar-primary: var(--sidebar-primary);
- --color-sidebar-foreground: var(--sidebar-foreground);
- --color-sidebar: var(--sidebar);
- --color-chart-5: var(--chart-5);
- --color-chart-4: var(--chart-4);
- --color-chart-3: var(--chart-3);
- --color-chart-2: var(--chart-2);
- --color-chart-1: var(--chart-1);
- --color-ring: var(--ring);
- --color-input: var(--input);
- --color-border: var(--border);
- --color-destructive: var(--destructive);
- --color-accent-foreground: var(--accent-foreground);
- --color-accent: var(--accent);
- --color-muted-foreground: var(--muted-foreground);
- --color-muted: var(--muted);
- --color-secondary-foreground: var(--secondary-foreground);
- --color-secondary: var(--secondary);
- --color-primary-foreground: var(--primary-foreground);
- --color-primary: var(--primary);
- --color-popover-foreground: var(--popover-foreground);
- --color-popover: var(--popover);
- --color-card-foreground: var(--card-foreground);
--color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+
+ /* Luxe Stay Design Tokens - Clean High Contrast Luxury */
+ --color-surface: #ffffff;
+ --color-on-surface: #0f172a;
+ --color-on-surface-variant: #475569;
+ --color-surface-container-lowest: #ffffff;
+ --color-surface-container-low: #f8fafc;
+ --color-surface-container: #f1f5f9;
+ --color-surface-container-high: #e2e8f0;
+ --color-surface-container-highest: #cbd5e1;
+ --color-surface-variant: #f1f5f9;
+ --color-primary-container: #1e293b;
+ --color-on-primary-container: #f8fafc;
+ --color-on-primary: #ffffff;
+ --color-secondary-container: #e2e8f0;
+ --color-on-secondary-container: #0f172a;
+ --color-tertiary: #2d1d00;
+ --color-on-tertiary: #ffffff;
+ --color-tertiary-container: #493100;
+ --color-on-tertiary-container: #cb9524;
+ --color-tertiary-fixed: #ffdeaa;
+ --color-tertiary-fixed-dim: #f8bc4b;
+ --color-outline: #64748b;
+ --color-outline-variant: #cbd5e1;
+ --color-inverse-surface: #0f172a;
+ --color-inverse-on-surface: #f8fafc;
+ --color-primary-fixed-dim: #94a3b8;
+
+ /* Typography */
+ --font-sans: var(--font-inter);
+ --font-heading: var(--font-montserrat);
+ --font-label-sm: var(--font-inter);
+ --font-headline-md: var(--font-montserrat);
+ --font-display-lg-mobile: var(--font-montserrat);
+ --font-display-lg: var(--font-montserrat);
+ --font-body-lg: var(--font-inter);
+ --font-body-md: var(--font-inter);
+
+ /* Spacing */
+ --spacing-margin-desktop: 48px;
+ --spacing-margin-mobile: 16px;
+ --spacing-container-max: 1280px;
+ --spacing-stack-sm: 8px;
+ --spacing-stack-md: 24px;
+ --spacing-stack-lg: 48px;
+ --spacing-gutter: 24px;
+
+ /* Radius */
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) * 1.4);
- --radius-2xl: calc(var(--radius) * 1.8);
- --radius-3xl: calc(var(--radius) * 2.2);
- --radius-4xl: calc(var(--radius) * 2.6);
}
:root {
- --background: oklch(1 0 0);
- --foreground: oklch(0.145 0 0);
- --card: oklch(1 0 0);
- --card-foreground: oklch(0.145 0 0);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.145 0 0);
- --primary: oklch(0.205 0 0);
- --primary-foreground: oklch(0.985 0 0);
- --secondary: oklch(0.97 0 0);
- --secondary-foreground: oklch(0.205 0 0);
- --muted: oklch(0.97 0 0);
- --muted-foreground: oklch(0.556 0 0);
- --accent: oklch(0.97 0 0);
- --accent-foreground: oklch(0.205 0 0);
- --destructive: oklch(0.577 0.245 27.325);
- --border: oklch(0.922 0 0);
- --input: oklch(0.922 0 0);
- --ring: oklch(0.708 0 0);
- --chart-1: oklch(0.87 0 0);
- --chart-2: oklch(0.556 0 0);
- --chart-3: oklch(0.439 0 0);
- --chart-4: oklch(0.371 0 0);
- --chart-5: oklch(0.269 0 0);
- --radius: 0.625rem;
- --sidebar: oklch(0.985 0 0);
- --sidebar-foreground: oklch(0.145 0 0);
- --sidebar-primary: oklch(0.205 0 0);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.97 0 0);
- --sidebar-accent-foreground: oklch(0.205 0 0);
- --sidebar-border: oklch(0.922 0 0);
- --sidebar-ring: oklch(0.708 0 0);
+ /* Mapping Shadcn to Luxe Stay */
+ --background: #ffffff;
+ --foreground: #0f172a;
+ --card: #ffffff;
+ --card-foreground: #0f172a;
+ --popover: #ffffff;
+ --popover-foreground: #0f172a;
+ --primary: #0f172a;
+ --primary-foreground: #ffffff;
+ --secondary: #f1f5f9;
+ --secondary-foreground: #0f172a;
+ --muted: #f8fafc;
+ --muted-foreground: #64748b;
+ --accent: #f1f5f9;
+ --accent-foreground: #0f172a;
+ --destructive: #ef4444;
+ --destructive-foreground: #ffffff;
+ --border: #e2e8f0;
+ --input: #c4c6cf;
+ --ring: #002045;
+ --radius: 0.5rem;
}
.dark {
- --background: oklch(0.145 0 0);
- --foreground: oklch(0.985 0 0);
- --card: oklch(0.205 0 0);
- --card-foreground: oklch(0.985 0 0);
- --popover: oklch(0.205 0 0);
- --popover-foreground: oklch(0.985 0 0);
- --primary: oklch(0.922 0 0);
- --primary-foreground: oklch(0.205 0 0);
- --secondary: oklch(0.269 0 0);
- --secondary-foreground: oklch(0.985 0 0);
- --muted: oklch(0.269 0 0);
- --muted-foreground: oklch(0.708 0 0);
- --accent: oklch(0.269 0 0);
- --accent-foreground: oklch(0.985 0 0);
- --destructive: oklch(0.704 0.191 22.216);
- --border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
- --ring: oklch(0.556 0 0);
- --chart-1: oklch(0.87 0 0);
- --chart-2: oklch(0.556 0 0);
- --chart-3: oklch(0.439 0 0);
- --chart-4: oklch(0.371 0 0);
- --chart-5: oklch(0.269 0 0);
- --sidebar: oklch(0.205 0 0);
- --sidebar-foreground: oklch(0.985 0 0);
- --sidebar-primary: oklch(0.488 0.243 264.376);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.269 0 0);
- --sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(1 0 0 / 10%);
- --sidebar-ring: oklch(0.556 0 0);
+ /* Inverse for Dark Mode */
+ --background: #020617;
+ --foreground: #f8fafc;
+ --card: #0f172a;
+ --card-foreground: #f8fafc;
+ --popover: #0f172a;
+ --popover-foreground: #f8fafc;
+ --primary: #f8fafc;
+ --primary-foreground: #0f172a;
+ --secondary: #1e293b;
+ --secondary-foreground: #f8fafc;
+ --muted: #1e293b;
+ --muted-foreground: #94a3b8;
+ --accent: #1e293b;
+ --accent-foreground: #f8fafc;
+ --destructive: #7f1d1d;
+ --destructive-foreground: #f8fafc;
+ --border: #1e293b;
+ --input: #1e293b;
+ --ring: #f8fafc;
}
@layer base {
@@ -122,9 +131,27 @@
@apply border-border outline-ring/50;
}
body {
- @apply bg-background text-foreground;
+ @apply bg-surface text-on-surface font-body-md;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
html {
@apply font-sans;
+ scroll-behavior: smooth;
+ }
+ ::selection {
+ @apply bg-primary text-primary-foreground;
+ }
+}
+
+/* Accessibility: Respect reduced motion preferences */
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ scroll-behavior: auto !important;
}
}
\ No newline at end of file
diff --git a/components/contact-content.tsx b/components/contact-content.tsx
new file mode 100644
index 0000000..e9dbfee
--- /dev/null
+++ b/components/contact-content.tsx
@@ -0,0 +1,107 @@
+"use client";
+
+import * as React from "react";
+import { motion } from "framer-motion";
+import { mockData } from "@/lib/mock-data";
+import { useTranslations } from "next-intl";
+import { toast } from "sonner";
+import { Loader2 } from "lucide-react";
+
+export function ContactContent() {
+ const t = useTranslations("contact");
+ const [isLoading, setIsLoading] = React.useState(false);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setIsLoading(true);
+
+ // Simulate network request
+ await new Promise((resolve) => setTimeout(resolve, 1500));
+
+ setIsLoading(false);
+ toast.success("Talebiniz başarıyla alındı!", {
+ description: "En kısa sürede size dönüş yapacağız.",
+ });
+ };
+
+ return (
+
+ {/* Decorative background blob */}
+
+
+
+ {t("title")}
+
+
+
+ {/* Contact Info */}
+
+ {t("title")}
+
+
+
+
{t("email")}
+
{mockData.contact.email}
+
+
+
{t("phone")}
+
{mockData.contact.phone}
+
+
+
{t("address")}
+
{mockData.contact.address}
+
+
+
+
+ {/* Contact Form */}
+
+ {t("reservationReq")}
+
+
+
+
+
+ );
+}
diff --git a/components/footer.tsx b/components/footer.tsx
new file mode 100644
index 0000000..35e8ea4
--- /dev/null
+++ b/components/footer.tsx
@@ -0,0 +1,82 @@
+"use client";
+
+import { useTranslations } from "next-intl";
+import Link from "next/link";
+import { mockData } from "@/lib/mock-data";
+import { Share2, Mail } from "lucide-react";
+
+const FacebookIcon = ({ className }: { className?: string }) => (
+
+
+
+);
+
+const InstagramIcon = ({ className }: { className?: string }) => (
+
+
+
+
+
+);
+
+export function Footer() {
+ const t = useTranslations("footer");
+
+ return (
+
+ );
+}
diff --git a/components/home/hero.tsx b/components/home/hero.tsx
new file mode 100644
index 0000000..91c243f
--- /dev/null
+++ b/components/home/hero.tsx
@@ -0,0 +1,108 @@
+"use client";
+
+import { useTranslations } from "next-intl";
+import { motion } from "framer-motion";
+import { mockData } from "@/lib/mock-data";
+import Link from "next/link";
+import Image from "next/image";
+
+export function Hero() {
+ const t = useTranslations("hero");
+
+ return (
+ <>
+
+ {/* Background Image - Using Next.js Image for optimization */}
+
+
+
+
+ {/* Gradient Overlay */}
+
+
+
+ {/* Hero Content */}
+
+
+
+ {t("subtitle")}
+
+
+ {t("title")}
+
+
+ {t("desc")}
+
+
+
+ {t("explore")}
+
+
+ {t("virtualTour")}
+
+
+
+
+
+
+ {/* Search Bar Utility (Floating over Hero bottom) */}
+
+ >
+ );
+}
diff --git a/components/home/newsletter.tsx b/components/home/newsletter.tsx
new file mode 100644
index 0000000..87a505e
--- /dev/null
+++ b/components/home/newsletter.tsx
@@ -0,0 +1,51 @@
+"use client";
+
+import { useTranslations } from "next-intl";
+import { motion } from "framer-motion";
+
+export function Newsletter() {
+ const t = useTranslations("newsletter");
+
+ return (
+
+ {/* Decorative Background Elements */}
+
+
+
+
+ {t("title")}
+
+
+ {t("desc")}
+
+
+
+
+
+ );
+}
diff --git a/components/home/room-list.tsx b/components/home/room-list.tsx
new file mode 100644
index 0000000..621c476
--- /dev/null
+++ b/components/home/room-list.tsx
@@ -0,0 +1,116 @@
+"use client";
+
+import { useTranslations } from "next-intl";
+import { mockData } from "@/lib/mock-data";
+import Link from "next/link";
+import Image from "next/image";
+import { motion } from "framer-motion";
+import { ArrowRight } from "lucide-react";
+
+export function RoomList() {
+ const t = useTranslations("accommodations");
+
+ // We take the first 3 rooms for the bento grid
+ const featuredRoom = mockData.accommodations[0];
+ const sideRooms = mockData.accommodations.slice(1, 3);
+
+ return (
+
+
+
+ {t("title")}
+ {t("desc")}
+
+
+
+
+ {t("viewAll")}
+
+
+
+
+
+ {/* Bento-style Grid */}
+
+
+
+
+
+
+
+
+
+
+ {t("bestseller")}
+
+
+
{featuredRoom.name}
+
+ {featuredRoom.type} • {t("bedrooms", { count: featuredRoom.bedrooms })} • {t("persons", { count: featuredRoom.capacity })}
+
+
+
+
+ {/* Side Stacked Items */}
+
+ {sideRooms.map((room) => (
+
+
+
+
+
+
+
{room.type}
+
{room.name}
+
+ {t("persons", { count: room.capacity })}
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/components/home/services.tsx b/components/home/services.tsx
new file mode 100644
index 0000000..06c2473
--- /dev/null
+++ b/components/home/services.tsx
@@ -0,0 +1,107 @@
+"use client";
+
+import { useTranslations } from "next-intl";
+import { motion } from "framer-motion";
+import { Waves, UtensilsCrossed, ConciergeBell } from "lucide-react";
+
+export function Services() {
+ const t = useTranslations("services");
+
+ const container = {
+ hidden: { opacity: 0 },
+ show: {
+ opacity: 1,
+ transition: {
+ staggerChildren: 0.15,
+ }
+ }
+ };
+
+ const item = {
+ hidden: { opacity: 0, y: 20 },
+ show: { opacity: 1, y: 0, transition: { duration: 0.5, ease: "easeOut" } }
+ };
+
+ const services = [
+ {
+ icon: Waves,
+ title: t("service1_title"),
+ desc: t("service1_desc"),
+ },
+ {
+ icon: UtensilsCrossed,
+ title: t("service2_title"),
+ desc: t("service2_desc"),
+ },
+ {
+ icon: ConciergeBell,
+ title: t("service3_title"),
+ desc: t("service3_desc"),
+ },
+ ];
+
+ return (
+
+
+
+
+ {t("subtitle")}
+
+
+ {t("title")}
+
+
+ {t("desc")}
+
+
+
+
+ {services.map((service) => (
+
+
+
+
+
+
{service.title}
+
+ {service.desc}
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/components/navbar.tsx b/components/navbar.tsx
new file mode 100644
index 0000000..b60cec6
--- /dev/null
+++ b/components/navbar.tsx
@@ -0,0 +1,45 @@
+"use client";
+
+import { useTranslations } from "next-intl";
+import Link from "next/link";
+import Image from "next/image";
+import { mockData } from "@/lib/mock-data";
+import { ThemeToggle } from "@/components/theme-toggle";
+
+export function Navbar() {
+ const t = useTranslations("nav");
+
+ return (
+
+ );
+}
diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx
new file mode 100644
index 0000000..ec6d108
--- /dev/null
+++ b/components/theme-provider.tsx
@@ -0,0 +1,90 @@
+"use client"
+
+import * as React from "react"
+
+type Theme = "light" | "dark" | "system"
+
+const ThemeContext = React.createContext<{
+ theme: Theme
+ setTheme: (theme: Theme) => void
+ resolvedTheme: "light" | "dark"
+}>({
+ theme: "system",
+ setTheme: () => {},
+ resolvedTheme: "light",
+})
+
+export function useTheme() {
+ return React.useContext(ThemeContext)
+}
+
+function getSystemTheme(): "light" | "dark" {
+ if (typeof window === "undefined") return "light"
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
+}
+
+export function ThemeProvider({
+ children,
+ attribute = "class",
+ defaultTheme = "system",
+ enableSystem = true,
+}: {
+ children: React.ReactNode
+ attribute?: string
+ defaultTheme?: Theme
+ enableSystem?: boolean
+ disableTransitionOnChange?: boolean
+}) {
+ const [theme, setThemeState] = React.useState(defaultTheme)
+ const [resolvedTheme, setResolvedTheme] = React.useState<"light" | "dark">("light")
+ const [mounted, setMounted] = React.useState(false)
+
+ // Read saved theme from localStorage on mount
+ React.useEffect(() => {
+ const saved = localStorage.getItem("theme") as Theme | null
+ if (saved) {
+ setThemeState(saved)
+ }
+ setMounted(true)
+ }, [])
+
+ // Resolve system theme and apply to document
+ React.useEffect(() => {
+ const resolved = theme === "system" ? getSystemTheme() : theme
+ setResolvedTheme(resolved)
+
+ const root = document.documentElement
+ root.classList.remove("light", "dark")
+ root.classList.add(resolved)
+ }, [theme])
+
+ // Listen for system theme changes
+ React.useEffect(() => {
+ if (!enableSystem) return
+
+ const mql = window.matchMedia("(prefers-color-scheme: dark)")
+ const handler = () => {
+ if (theme === "system") {
+ const resolved = getSystemTheme()
+ setResolvedTheme(resolved)
+ const root = document.documentElement
+ root.classList.remove("light", "dark")
+ root.classList.add(resolved)
+ }
+ }
+
+ mql.addEventListener("change", handler)
+ return () => mql.removeEventListener("change", handler)
+ }, [theme, enableSystem])
+
+ const setTheme = React.useCallback((newTheme: Theme) => {
+ setThemeState(newTheme)
+ localStorage.setItem("theme", newTheme)
+ }, [])
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx
new file mode 100644
index 0000000..d34e474
--- /dev/null
+++ b/components/theme-toggle.tsx
@@ -0,0 +1,54 @@
+"use client"
+
+import * as React from "react"
+import { Moon, Sun } from "lucide-react"
+import { useTheme } from "@/components/theme-provider"
+import { motion } from "framer-motion"
+
+export function ThemeToggle() {
+ const { theme, setTheme, resolvedTheme } = useTheme()
+ const [mounted, setMounted] = React.useState(false)
+
+ React.useEffect(() => {
+ setMounted(true)
+ }, [])
+
+ if (!mounted) {
+ return
+ }
+
+ const isDark = resolvedTheme === "dark"
+
+ return (
+ setTheme(isDark ? "light" : "dark")}
+ className="relative p-2 rounded-full bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors outline-none focus-visible:ring-2 focus-visible:ring-primary overflow-hidden"
+ aria-label="Toggle theme"
+ >
+
+
+
+
+
+
+
+ )
+}
diff --git a/lib/mock-data.ts b/lib/mock-data.ts
new file mode 100644
index 0000000..13b9cd1
--- /dev/null
+++ b/lib/mock-data.ts
@@ -0,0 +1,82 @@
+export const mockData = {
+ logos: {
+ main: "https://kordonaparts.com/tema/assets/images/logo.png",
+ footer: "https://kordonaparts.com/tema/assets/images/footer-logo.png"
+ },
+ heroSlides: [
+ "https://kordonaparts.com/media/images/slide/1669728563.jpg",
+ "https://kordonaparts.com/media/images/slide/1669728527.jpg",
+ "https://kordonaparts.com/media/images/slide/1669728539.jpg",
+ "https://kordonaparts.com/media/images/slide/1669728553.jpg",
+ "https://kordonaparts.com/media/images/slide/1669728576.jpg"
+ ],
+ contact: {
+ email: "info@kordonapart.com",
+ phone: "+90 532 345 15 96",
+ address: "Atatürk Cad. No:10 48300 Fethiye MUĞLA TURKEY",
+ social: {
+ facebook: "https://www.facebook.com/fethiyekordonapart/",
+ twitter: "https://twitter.com/kordonaparts",
+ instagram: "https://www.instagram.com/kordonaparts",
+ youtube: "https://www.youtube.com/kordonaparts"
+ }
+ },
+ accommodations: [
+ {
+ id: "kordon-apart-11",
+ slug: "kordon-apart-11",
+ name: "Kordon Apart 11",
+ type: "Delüks Apart",
+ bedrooms: 2,
+ capacity: 4,
+ location: "Fethiye / Merkez",
+ image: "https://kordonaparts.com/media/images/villa/kordon-apart-1.jpg"
+ },
+ {
+ id: "kordon-apart-21",
+ slug: "kordon-apart-21",
+ name: "Kordon Apart 21",
+ type: "Premium Apart Oda",
+ bedrooms: 1,
+ capacity: 3,
+ location: "Fethiye / Merkez",
+ image: "https://kordonaparts.com/media/images/villa/kordon-apart-2.jpg"
+ },
+ {
+ id: "kordon-apart-22",
+ slug: "kordon-apart-22",
+ name: "Kordon Apart 22",
+ type: "Standart Apart Oda",
+ bedrooms: 1,
+ capacity: 2,
+ location: "Fethiye / Merkez",
+ image: "https://kordonaparts.com/media/images/villa/kordon-apart-3.jpg"
+ },
+ {
+ id: "kordon-apart-23",
+ slug: "kordon-apart-23",
+ name: "Kordon Apart 23",
+ type: "Standart Apart Oda",
+ bedrooms: 1,
+ capacity: 3,
+ location: "Fethiye / Merkez",
+ image: "https://kordonaparts.com/media/images/villa/kordon-apart-3.jpg" // Using 22's image as fallback since PRD had lazy.png
+ },
+ {
+ id: "kordon-apart-12",
+ slug: "kordon-apart-12",
+ name: "Kordon Apart 12",
+ type: "Meerblick",
+ bedrooms: 2,
+ capacity: 4,
+ location: "Fethiye / Merkez",
+ image: "https://kordonaparts.com/media/images/villa/kordon-apart-1.jpg" // Using 11's image as fallback since PRD had lazy.png
+ }
+ ],
+ accommodationTypes: [
+ "Delüks Apart",
+ "Premium Apart Oda",
+ "Standart Apart Oda",
+ "Meerblick"
+ ]
+};
diff --git a/messages/de.json b/messages/de.json
new file mode 100644
index 0000000..9371f2d
--- /dev/null
+++ b/messages/de.json
@@ -0,0 +1,46 @@
+{
+ "nav": {
+ "home": "Homepage",
+ "accommodations": "Unsere Unterkünfte",
+ "accommodationTypes": "Unterkunft",
+ "gallery": "Galerie",
+ "blog": "Blog",
+ "contact": "Kommunikation"
+ },
+ "hero": {
+ "title": "Kordon Apart",
+ "region": "Fethiye / Merkez",
+ "checkInCheckOut": "Check in - Check out Datum",
+ "persons": "Anzahl der Personen Auswählen",
+ "search": "Suche",
+ "reservation": "Reservierungsanfrage"
+ },
+ "accommodations": {
+ "title": "Unsere Unterkünfte",
+ "viewAll": "Alle anzeigen",
+ "bedrooms": "{count} Schlafzimmer",
+ "persons": "{count} Personen",
+ "view": "Ansehen"
+ },
+ "footer": {
+ "rights": "Alle Rechte vorbehalten.",
+ "contact": "Kontakt",
+ "pages": "Seiten",
+ "accommodations": "Unsere Unterkünfte",
+ "social": "Social-Media-Konten",
+ "cookiePolicy": "Cookie-Richtlinie",
+ "userAgreement": "Kullanıcı Sözleşmesi",
+ "privacyPolicy": "Gizlilik Politikası",
+ "about": "Über Uns",
+ "reviews": "Kundenrezensionen"
+ },
+ "contact": {
+ "title": "Kommunikation",
+ "email": "E-Mail-Adresse",
+ "phone": "Telefon",
+ "address": "Adresse",
+ "reservationReq": "Reservierungsanfrage",
+ "reservationNo": "Reservierungs-Nr.",
+ "send": "Senden"
+ }
+}
diff --git a/messages/en.json b/messages/en.json
index f793be6..777224d 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -1,14 +1,72 @@
{
"nav": {
"home": "Home",
- "about": "About Us",
- "contact": "Contact"
+ "accommodations": "Suites",
+ "gallery": "Gallery",
+ "contact": "Contact",
+ "bookNow": "BOOK NOW"
},
"hero": {
- "title": "Welcome",
- "cta": "Learn More"
+ "subtitle": "Boutique Hospitality",
+ "title": "Architecture of Serenity.",
+ "desc": "Experience curated living in the heart of Fethiye. Luxury is in the details we choose to leave out.",
+ "explore": "EXPLORE SUITES",
+ "virtualTour": "VIRTUAL TOUR",
+ "checkIn": "Check In",
+ "checkOut": "Check Out",
+ "guests": "Guests",
+ "checkAvailability": "CHECK AVAILABILITY",
+ "adult1": "1 Adult",
+ "adult2": "2 Adults",
+ "adult3": "3+ Adults"
+ },
+ "accommodations": {
+ "title": "Curated Residences",
+ "desc": "Each space is a unique architectural study.",
+ "viewAll": "VIEW ALL SUITES",
+ "bedrooms": "{count} Bedrooms",
+ "persons": "{count} Persons",
+ "bestseller": "Bestseller"
+ },
+ "services": {
+ "subtitle": "The Experience",
+ "title": "Beyond the Four Walls",
+ "desc": "We curate moments of stillness and connection through bespoke services designed for the modern nomad.",
+ "service1_title": "Rooftop Wellness",
+ "service1_desc": "A heated infinity pool and sauna overlooking the historical district skyline.",
+ "service2_title": "In-Suite Dining",
+ "service2_desc": "Chef-prepared meals delivered to your door, celebrating local seasonal ingredients.",
+ "service3_title": "Digital Concierge",
+ "service3_desc": "24/7 contactless assistance for bookings, transport, and local recommendations."
+ },
+ "newsletter": {
+ "title": "Join the Luxe Collective",
+ "desc": "Be the first to know about new property openings and exclusive seasonal offers.",
+ "placeholder": "YOUR EMAIL ADDRESS",
+ "subscribe": "SUBSCRIBE",
+ "label": "Your email address"
},
"footer": {
- "rights": "All rights reserved."
+ "rights": "All rights reserved.",
+ "contact": "Contact",
+ "pages": "Pages",
+ "accommodations": "Suites",
+ "social": "Social Media Accounts",
+ "cookiePolicy": "Cookie Policy",
+ "userAgreement": "User Agreement",
+ "privacyPolicy": "Privacy Policy",
+ "about": "About Us",
+ "reviews": "Customer Reviews",
+ "legal": "Legal",
+ "desc": "Boutique apart-hotels redefined through architectural clarity and premium hospitality."
+ },
+ "contact": {
+ "title": "Contact",
+ "email": "E-mail Address",
+ "phone": "Phone",
+ "address": "Address",
+ "reservationReq": "Reservation Request",
+ "reservationNo": "Reservation No",
+ "send": "Send"
}
}
diff --git a/messages/ru.json b/messages/ru.json
new file mode 100644
index 0000000..0851259
--- /dev/null
+++ b/messages/ru.json
@@ -0,0 +1,46 @@
+{
+ "nav": {
+ "home": "Главная",
+ "accommodations": "Наши апартаменты",
+ "accommodationTypes": "Проживание",
+ "gallery": "Галерея",
+ "blog": "Блог",
+ "contact": "Контакты"
+ },
+ "hero": {
+ "title": "Kordon Apart",
+ "region": "Фетхие / Центр",
+ "checkInCheckOut": "Дата заезда - выезда",
+ "persons": "Количество человек",
+ "search": "Поиск",
+ "reservation": "Запрос на бронирование"
+ },
+ "accommodations": {
+ "title": "Наши апартаменты",
+ "viewAll": "Смотреть все",
+ "bedrooms": "{count} Спальни",
+ "persons": "{count} Человек",
+ "view": "Смотреть"
+ },
+ "footer": {
+ "rights": "Все права защищены.",
+ "contact": "Контакты",
+ "pages": "Страницы",
+ "accommodations": "Наши апартаменты",
+ "social": "Социальные сети",
+ "cookiePolicy": "Политика использования файлов cookie",
+ "userAgreement": "Пользовательское соглашение",
+ "privacyPolicy": "Политика конфиденциальности",
+ "about": "О нас",
+ "reviews": "Отзывы клиентов"
+ },
+ "contact": {
+ "title": "Контакты",
+ "email": "Адрес электронной почты",
+ "phone": "Телефон",
+ "address": "Адрес",
+ "reservationReq": "Запрос на бронирование",
+ "reservationNo": "Номер бронирования",
+ "send": "Отправить"
+ }
+}
diff --git a/messages/tr.json b/messages/tr.json
index 1994c6c..ac56d34 100644
--- a/messages/tr.json
+++ b/messages/tr.json
@@ -1,14 +1,72 @@
{
"nav": {
"home": "Ana Sayfa",
- "about": "Hakkımızda",
- "contact": "İletişim"
+ "accommodations": "Odalarımız",
+ "gallery": "Galeri",
+ "contact": "İletişim",
+ "bookNow": "REZERVASYON"
},
"hero": {
- "title": "Hoş Geldiniz",
- "cta": "Daha Fazla Bilgi"
+ "subtitle": "Butik Konaklama",
+ "title": "Huzurun Mimarisi.",
+ "desc": "Fethiye'nin kalbinde özenle seçilmiş bir yaşamı deneyimleyin. Lüks, dışarıda bıraktığımız detaylarda gizlidir.",
+ "explore": "ODALARI KEŞFET",
+ "virtualTour": "SANAL TUR",
+ "checkIn": "Giriş Tarihi",
+ "checkOut": "Çıkış Tarihi",
+ "guests": "Misafirler",
+ "checkAvailability": "MÜSAİTLİK KONTROLÜ",
+ "adult1": "1 Yetişkin",
+ "adult2": "2 Yetişkin",
+ "adult3": "3+ Yetişkin"
+ },
+ "accommodations": {
+ "title": "Odalarımız",
+ "desc": "Her mekan benzersiz bir mimari çalışmadır.",
+ "viewAll": "TÜM ODALARI GÖR",
+ "bedrooms": "{count} Yatak Odası",
+ "persons": "{count} Kişi",
+ "bestseller": "En Çok Tercih Edilen"
+ },
+ "services": {
+ "subtitle": "Deneyim",
+ "title": "Dört Duvarın Ötesinde",
+ "desc": "Modern göçebeler için tasarlanmış özel hizmetlerle dinginlik ve bağlantı anları yaratıyoruz.",
+ "service1_title": "Teras Wellness",
+ "service1_desc": "Tarihi bölge silüetine bakan ısıtmalı sonsuzluk havuzu ve sauna.",
+ "service2_title": "Oda İçi Yemek",
+ "service2_desc": "Yerel mevsimlik malzemeleri kutlayan, kapınıza teslim şef yapımı yemekler.",
+ "service3_title": "Dijital Concierge",
+ "service3_desc": "Rezervasyonlar, ulaşım ve yerel öneriler için 7/24 temassız asistan."
+ },
+ "newsletter": {
+ "title": "Luxe Collective'e Katılın",
+ "desc": "Yeni tesis açılışları ve özel sezonluk tekliflerden ilk siz haberdar olun.",
+ "placeholder": "E-POSTA ADRESİNİZ",
+ "subscribe": "ABONE OL",
+ "label": "E-posta adresiniz"
},
"footer": {
- "rights": "Tüm hakları saklıdır."
+ "rights": "Tüm hakları saklıdır.",
+ "contact": "İletişim",
+ "pages": "Sayfalar",
+ "accommodations": "Odalarımız",
+ "social": "Sosyal Medya Hesaplarımız",
+ "cookiePolicy": "Çerez Politikası",
+ "userAgreement": "Kullanıcı Sözleşmesi",
+ "privacyPolicy": "Gizlilik Sözleşmesi",
+ "about": "Hakkımızda",
+ "reviews": "Müşteri Yorumları",
+ "legal": "Yasal",
+ "desc": "Mimari netlik ve üst düzey misafirperverlik ile yeniden tanımlanmış butik apart oteller."
+ },
+ "contact": {
+ "title": "İletişim",
+ "email": "E-Posta Adresi",
+ "phone": "Telefon",
+ "address": "Adres",
+ "reservationReq": "Rezervasyon Talebi",
+ "reservationNo": "Rezervasyon No",
+ "send": "Gönder"
}
}
diff --git a/next.config.ts b/next.config.ts
index 8dff980..9e638f3 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -9,6 +9,7 @@ const nextConfig: NextConfig = {
remotePatterns: [
{ protocol: 'https', hostname: 'res.cloudinary.com' },
{ protocol: 'https', hostname: 'images.unsplash.com' },
+ { protocol: 'https', hostname: 'kordonaparts.com' },
],
},
}
diff --git a/package-lock.json b/package-lock.json
index b36e112..9a57965 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,11 +1,11 @@
{
- "name": "fethiye-holiday",
+ "name": "kordonaparts",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "fethiye-holiday",
+ "name": "kordonaparts",
"version": "0.1.0",
"dependencies": {
"@base-ui/react": "^1.5.0",
@@ -19,9 +19,11 @@
"next": "16.2.9",
"next-auth": "^5.0.0-beta.31",
"next-intl": "^4.13.0",
+ "next-themes": "^0.4.6",
"react": "19.2.4",
"react-dom": "19.2.4",
"shadcn": "^4.11.0",
+ "sonner": "^2.0.7",
"tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0"
},
@@ -8590,6 +8592,16 @@
}
}
},
+ "node_modules/next-themes": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
+ "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -10223,6 +10235,16 @@
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"license": "MIT"
},
+ "node_modules/sonner": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
+ "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
diff --git a/package.json b/package.json
index 2e615fe..2947666 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "name": "fethiye-holiday",
+ "name": "kordonaparts",
"version": "0.1.0",
"private": true,
"scripts": {
@@ -20,9 +20,11 @@
"next": "16.2.9",
"next-auth": "^5.0.0-beta.31",
"next-intl": "^4.13.0",
+ "next-themes": "^0.4.6",
"react": "19.2.4",
"react-dom": "19.2.4",
"shadcn": "^4.11.0",
+ "sonner": "^2.0.7",
"tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0"
},