From a36b833e008cff8260bdf28d4bf1c13e03ae9cfb Mon Sep 17 00:00:00 2001 From: AyrisAI Date: Wed, 15 Jul 2026 14:42:51 +0300 Subject: [PATCH] feat: complete UI/UX improvements and localization --- app/[locale]/dairelerimiz/[slug]/page.tsx | 181 +++++++++ app/[locale]/dairelerimiz/page.tsx | 107 ++++++ app/[locale]/galeri/page.tsx | 182 +++++++++ app/[locale]/iletisim/page.tsx | 196 ++++++++++ app/[locale]/layout.tsx | 28 +- app/[locale]/page.tsx | 223 +++++++++-- app/globals.css | 235 ++++++------ components/BougainvilleaMotif.tsx | 14 + components/Footer.tsx | 39 ++ components/Header.tsx | 172 +++++++++ components/LocaleSwitcher.tsx | 39 ++ docs/design.md | 69 ++++ docs/prd.md | 187 ++++++++++ .../aegean_coastal_sanctuary/DESIGN.md | 175 +++++++++ .../code.html | 349 ++++++++++++++++++ .../screen.png | Bin 0 -> 292544 bytes .../gallery_sitar_apart_otel/code.html | 339 +++++++++++++++++ .../gallery_sitar_apart_otel/screen.png | Bin 0 -> 1158732 bytes .../code.html | 347 +++++++++++++++++ .../screen.png | Bin 0 -> 310449 bytes .../sitar_apart_otel_homepage/code.html | 315 ++++++++++++++++ .../sitar_apart_otel_homepage/screen.png | Bin 0 -> 954400 bytes lib/openinary-loader.ts | 39 ++ lib/openinary-url.ts | 5 + lib/openinary.ts | 19 + messages/de.json | 141 +++++++ messages/en.json | 137 ++++++- messages/tr.json | 137 ++++++- package-lock.json | 213 +++-------- package.json | 6 +- prisma.config.ts | 4 + prisma/schema.prisma | 117 ++++-- 32 files changed, 3667 insertions(+), 348 deletions(-) create mode 100644 app/[locale]/dairelerimiz/[slug]/page.tsx create mode 100644 app/[locale]/dairelerimiz/page.tsx create mode 100644 app/[locale]/galeri/page.tsx create mode 100644 app/[locale]/iletisim/page.tsx create mode 100644 components/BougainvilleaMotif.tsx create mode 100644 components/Footer.tsx create mode 100644 components/Header.tsx create mode 100644 components/LocaleSwitcher.tsx create mode 100644 docs/design.md create mode 100644 docs/prd.md create mode 100644 docs/stitch_akyaka_golden_hour_retreat/aegean_coastal_sanctuary/DESIGN.md create mode 100644 docs/stitch_akyaka_golden_hour_retreat/apartment_details_sitar_apart_otel/code.html create mode 100644 docs/stitch_akyaka_golden_hour_retreat/apartment_details_sitar_apart_otel/screen.png create mode 100644 docs/stitch_akyaka_golden_hour_retreat/gallery_sitar_apart_otel/code.html create mode 100644 docs/stitch_akyaka_golden_hour_retreat/gallery_sitar_apart_otel/screen.png create mode 100644 docs/stitch_akyaka_golden_hour_retreat/location_contact_sitar_apart_otel/code.html create mode 100644 docs/stitch_akyaka_golden_hour_retreat/location_contact_sitar_apart_otel/screen.png create mode 100644 docs/stitch_akyaka_golden_hour_retreat/sitar_apart_otel_homepage/code.html create mode 100644 docs/stitch_akyaka_golden_hour_retreat/sitar_apart_otel_homepage/screen.png create mode 100644 lib/openinary-loader.ts create mode 100644 lib/openinary-url.ts create mode 100644 lib/openinary.ts create mode 100644 messages/de.json create mode 100644 prisma.config.ts diff --git a/app/[locale]/dairelerimiz/[slug]/page.tsx b/app/[locale]/dairelerimiz/[slug]/page.tsx new file mode 100644 index 0000000..b71fd0c --- /dev/null +++ b/app/[locale]/dairelerimiz/[slug]/page.tsx @@ -0,0 +1,181 @@ +import { getTranslations, setRequestLocale } from 'next-intl/server' +import { Link } from '@/i18n/routing' +import Image from 'next/image' +import { notFound } from 'next/navigation' +import { CheckCircle2, Maximize, Users, BedDouble, ArrowRight } from 'lucide-react' + +// Bu sayfa parametre olarak dinamik slug alacak +export default async function RoomDetailPage({ params }: { params: Promise<{ locale: string, slug: string }> }) { + const { locale, slug } = await params + setRequestLocale(locale) + + const tRooms = await getTranslations('rooms') + + const MOCK_ROOMS = [ + { + slug: '1-0-daire', + name: tRooms('room_list.1_0_daire.name'), + description: tRooms('room_list.1_0_daire.desc'), + capacity: 2, + size: '25m²', + beds: '1', + image: 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?auto=format&fit=crop&q=80', + gallery: [ + 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&q=80', + 'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?auto=format&fit=crop&q=80', + 'https://images.unsplash.com/photo-1582719508461-905c673771fd?auto=format&fit=crop&q=80' + ], + amenities: ['Mini Mutfak', 'Klima', 'Balkon', 'WiFi', 'TV', 'Saç Kurutma Makinesi'] + }, + { + slug: '1-1-daire-a', + name: tRooms('room_list.1_1_daire_a.name'), + description: tRooms('room_list.1_1_daire_a.desc'), + capacity: 2, + size: '40m²', + beds: '1 + Sofa', + image: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&q=80', + gallery: [ + 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?auto=format&fit=crop&q=80', + 'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?auto=format&fit=crop&q=80', + 'https://images.unsplash.com/photo-1582719508461-905c673771fd?auto=format&fit=crop&q=80' + ], + amenities: ['Ayrı Mutfak', 'Klima', 'Balkon', 'WiFi', 'Oturma Alanı', 'Çamaşır Makinesi'] + }, + { + slug: '1-1-daire-b', + name: tRooms('room_list.1_1_daire_b.name'), + description: tRooms('room_list.1_1_daire_b.desc'), + capacity: 4, + size: '50m²', + beds: '2', + image: 'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?auto=format&fit=crop&q=80', + gallery: [ + 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?auto=format&fit=crop&q=80', + 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&q=80', + 'https://images.unsplash.com/photo-1582719508461-905c673771fd?auto=format&fit=crop&q=80' + ], + amenities: ['2 Oda', 'Ayrı Mutfak', 'Klima', 'Balkon', 'WiFi', 'Geniş Oturma Alanı'] + } + ]; + + const room = MOCK_ROOMS.find(r => r.slug === slug); + + if (!room) { + notFound(); + } + + return ( +
+ {/* Hero Section */} +
+ {room.name} +
+
+
+

+ {room.name} +

+
+
+
+ + {/* Main Content */} +
+
+ + {/* Left Column - Details */} +
+ + {/* Badges */} +
+
+ + Maks {room.capacity} Kişi +
+
+ + {room.size} +
+
+ + {room.beds} Yatak +
+
+ + {/* Description */} +
+

+ {room.description} +

+
+ + {/* Amenities */} +
+

+ {tRooms('details.features')} +

+
+ {room.amenities.map((amenity, index) => ( +
+ + {amenity} +
+ ))} +
+
+ + {/* Mini Gallery */} +
+

+ {tRooms('details.gallery')} +

+
+ {room.gallery.map((img, i) => ( +
+ {`${room.name} +
+ ))} +
+
+ +
+ + {/* Right Column - Sticky Sidebar */} +
+
+

+ Bilgi Alın +

+

+ Tarihlerinize uygun fiyat ve müsaitlik durumunu öğrenmek için bizimle iletişime geçin. +

+ + + {tRooms('details.book_now_cta')} + + +
+
+ +
+
+
+ ) +} diff --git a/app/[locale]/dairelerimiz/page.tsx b/app/[locale]/dairelerimiz/page.tsx new file mode 100644 index 0000000..4956b64 --- /dev/null +++ b/app/[locale]/dairelerimiz/page.tsx @@ -0,0 +1,107 @@ +import { getTranslations, setRequestLocale } from 'next-intl/server' +import { Link } from '@/i18n/routing' +import Image from 'next/image' +import { Users, Square, Info } from 'lucide-react' + +export default async function RoomsPage({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params + setRequestLocale(locale) + + const tNav = await getTranslations('nav') + const tRooms = await getTranslations('rooms') + + const MOCK_ROOMS = [ + { + slug: '1-0-daire', + name: tRooms('room_list.1_0_daire.name'), + description: tRooms('room_list.1_0_daire.desc'), + capacity: 2, + size: '25m²', + image: 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?auto=format&fit=crop&q=80', + amenities: ['Mini Mutfak', 'Klima', 'Balkon', 'WiFi'] + }, + { + slug: '1-1-daire-a', + name: tRooms('room_list.1_1_daire_a.name'), + description: tRooms('room_list.1_1_daire_a.desc'), + capacity: 2, + size: '40m²', + image: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&q=80', + amenities: ['Ayrı Mutfak', 'Klima', 'Balkon', 'WiFi', 'Oturma Alanı'] + }, + { + slug: '1-1-daire-b', + name: tRooms('room_list.1_1_daire_b.name'), + description: tRooms('room_list.1_1_daire_b.desc'), + capacity: 4, + size: '50m²', + image: 'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?auto=format&fit=crop&q=80', + amenities: ['2 Oda', 'Ayrı Mutfak', 'Klima', 'Balkon', 'WiFi'] + } + ]; + + return ( +
+
+
+

{tRooms('title')}

+

+ {tRooms('description')} +

+
+ +
+ {MOCK_ROOMS.map((room) => ( +
+
+ {room.name} +
+
+
+

{room.name}

+
+ +

+ {room.description} +

+ +
+
+ + {room.capacity} {tRooms('capacity')} +
+
+ + {room.size} +
+
+ + {room.amenities.length} {tRooms('amenities')} +
+
+ +
+
+ {tRooms('ask_price')} {tRooms('per_night')} +
+ + {tRooms('details_btn')} + +
+
+
+ ))} +
+
+
+ ) +} diff --git a/app/[locale]/galeri/page.tsx b/app/[locale]/galeri/page.tsx new file mode 100644 index 0000000..64486c8 --- /dev/null +++ b/app/[locale]/galeri/page.tsx @@ -0,0 +1,182 @@ +'use client' + +import { useState } from 'react' +import { motion, AnimatePresence } from 'framer-motion' +import Image from 'next/image' +import BougainvilleaMotif from '@/components/BougainvilleaMotif' +import { useTranslations } from 'next-intl' +import { X } from 'lucide-react' + +export default function GalleryPage() { + const t = useTranslations('gallery') + const [activeFilter, setActiveFilter] = useState('all') + const [selectedImage, setSelectedImage] = useState(null) + + const filters = [ + { id: 'all', label: t('filters.all') }, + { id: 'rooms', label: t('filters.rooms') }, + { id: 'exterior', label: t('filters.exterior') }, + { id: 'view', label: t('filters.view') }, + { id: 'balcony', label: t('filters.balcony') }, + ] + + const galleryItems = [ + { + id: 1, + category: 'rooms', + title: t('items.1'), + src: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&q=80', + colSpan: 'col-span-1', + }, + { + id: 2, + category: 'exterior', + title: t('items.2'), + src: 'https://images.unsplash.com/photo-1596394516093-501ba68a0ba6?auto=format&fit=crop&q=80', + colSpan: 'col-span-1 md:col-span-2 lg:col-span-1', + }, + { + id: 3, + category: 'view', + title: t('items.3'), + src: 'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?auto=format&fit=crop&q=80', + colSpan: 'col-span-1', + }, + { + id: 4, + category: 'balcony', + title: t('items.4'), + src: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&q=80', + colSpan: 'col-span-1 md:col-span-2', + }, + { + id: 5, + category: 'rooms', + title: t('items.5'), + src: 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?auto=format&fit=crop&q=80', + colSpan: 'col-span-1', + }, + { + id: 6, + category: 'view', + title: t('items.6'), + src: 'https://images.unsplash.com/photo-1582719508461-905c673771fd?auto=format&fit=crop&q=80', + colSpan: 'col-span-1 md:col-span-2 lg:col-span-1', + }, + ] + + const filteredItems = galleryItems.filter(item => + activeFilter === 'all' || item.category === activeFilter + ) + + return ( +
+ {/* Header Section */} +
+
+ +
+

+ {t('title')} +

+

+ {t('description')} +

+
+ + {/* Filter Bar */} +
+ {filters.map((filter) => ( + + ))} +
+ + {/* Masonry-like Grid */} + + + {filteredItems.map((item) => ( + setSelectedImage(item.src)} + tabIndex={0} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setSelectedImage(item.src); + } + }} + > +
+ {item.title} +
+ + {item.title} + +
+
+
+ ))} +
+
+ + {/* Lightbox Overlay */} + + {selectedImage && ( + setSelectedImage(null)} + > + + e.stopPropagation()} + > + Enlarged view + + + )} + +
+ ) +} diff --git a/app/[locale]/iletisim/page.tsx b/app/[locale]/iletisim/page.tsx new file mode 100644 index 0000000..33eceee --- /dev/null +++ b/app/[locale]/iletisim/page.tsx @@ -0,0 +1,196 @@ +'use client' + +import { motion } from 'framer-motion' +import Image from 'next/image' +import BougainvilleaMotif from '@/components/BougainvilleaMotif' +import { MapPin, Waves, ShoppingBasket, Coffee, PersonStanding, Phone, Mail } from 'lucide-react' +import { useTranslations } from 'next-intl' + +const EASE: [number, number, number, number] = [0.22, 1, 0.36, 1] + +export default function ContactPage() { + const t = useTranslations('contact') + + return ( +
+ {/* Hero Section */} +
+ + + + +

{t('title')}

+
+ + {t('description')} + +
+ + {/* Location Grid */} +
+
+ + {/* Map Container */} + +
+ + {t('connect.map_placeholder')} +
+ + {/* Mock Map Interaction Layer */} +
+
+ +
+
+

{t('connect.address_title')}

+

{t('connect.address_value')}

+
+
+
+ + {/* Highlights List */} + +
+

{t('walking_distance')}

+
+
+ +
    + {[ + { icon: Waves, title: t('locations.beach.title'), desc: t('locations.beach.desc') }, + { icon: ShoppingBasket, title: t('locations.market.title'), desc: t('locations.market.desc') }, + { icon: Coffee, title: t('locations.cafe.title'), desc: t('locations.cafe.desc') }, + { icon: PersonStanding, title: t('locations.hiking.title'), desc: t('locations.hiking.desc') }, + ].map((item, idx) => ( +
  • + +
    +

    {item.title}

    +

    {item.desc}

    +
    +
  • + ))} +
+
+ +
+
+ + {/* Contact Section */} +
+
+ + {/* Contact Form Card */} + +

{t('form.title')}

+
e.preventDefault()}> +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ + +
+
+ + {/* Contact Details & Visual */} + +
+

{t('connect.title')}

+

+ {t('connect.description')} +

+
+ +
+
+
+ +
+
+

{t('connect.phone')}

+

+90 530 510 98 42

+
+
+ +
+
+ +
+
+

{t('connect.email')}

+

iletisim@sitarapart.com

+
+
+
+ + {/* Decorative Image */} +
+ Akyaka Details +
+
+ +
+
+
+ ) +} diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx index e64a5e9..a775aa5 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 { Literata, Nunito_Sans } 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 Header from '@/components/Header'; +import Footer from '@/components/Footer'; import "../globals.css"; -const geistSans = Geist({ - variable: "--font-geist-sans", +const nunito_sans = Nunito_Sans({ + variable: "--font-nunito-sans", subsets: ["latin"], + weight: ["400", "600", "700"], }); -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", +const literata = Literata({ + variable: "--font-literata", subsets: ["latin"], + weight: ["400", "500", "600", "700"], }); export const metadata: Metadata = { - title: "Boilerplate App", - description: "Next.js boilerplate with next-intl and NextAuth", + title: "Sitar Apart & Otel", + description: "Akyaka'nın Huzuru Sizinle!", }; export function generateStaticParams() { @@ -44,11 +48,15 @@ export default async function RootLayout({ return ( - + - {children} +
+
+ {children} +
+